I have a method with some logic in it and I'm not sure how to unit test it. Because it's a unit test for this specific method it should run without connecting to the database. I read about stubs and mockups but I can't find a way to apply them to this situation.
I would like to force the Client:GetClient to return the client object with the right properties so I can test each logic branch.
class ClientType {
function GetClientType($id) {
$objClient = Client::GetClient($id);
if ($objClient->Returning == 1) {
return 'returning';
}
else {
return 'normal';
}
}
This is the test I had in mind
class ResourceTest extends PHPUnit_Framework_TestCase {
function testGetClientType() {
$objClientType = new ClientType();
$this->assertTrue($objClientType->GetClientType(100), 'normal');
}
}
The problem is the dependency $objClient = Client::GetClient($id); The GetClient will pull a client from database but I need to replace this with a Stub so the unit tests work without real access to the database.
Conclusion
If you have code like the one presented: refactor it and use Dependency Injection.
If you have legacy code or just don't want to refactor try this solution: http://sebastian-bergmann.de/archives/885-Stubbing-Hard-Coded-Dependencies.html
With PHPUnit you can do
$class = $this->getMockClass(
'Client', /* name of class to mock */
array('getClient') /* list of methods to mock */
);
$class::staticExpects($this->any())
->method('getClient')
->will($this->returnValue('foo'));
In general, you want to avoid static methods though:
http://sebastian-bergmann.de/archives/883-Stubbing-and-Mocking-Static-Methods.html
https://kore-nordmann.de/blog/0103_static_considered_harmful.html
EDIT after update
PHPUnit can also stub hardcoded dependencies. See
Stubbing Hard-Coded Dependencies by Sebastian Bergmann
However, since you already noticed by now that it is a Pain the Behind to test statics and hardcoded dependencies, I suggest you remove the hardcoded dependency and static call with a real object that you inject into your ClientType instead.
Another option would be to use http://antecedent.github.io/patchwork (not affiliated with it), which
is a PHP library that makes it possible to redefine user-defined functions and methods
at runtime, loosely replicating the functionality runkit_function_redefine in pure PHP 5.3 code,
which, among other things, enables you to replace static and private methods with test doubles.
Try Mocking for such kind of a problem. I do not know PHP, but we do mocking in Java and C# in such a scenario.
EDIT
I'm sorry I didn't see that you were facing a problem with mocks. Well usually for Mocks you tell them what you expect from them and they respond to the same. LSV principle is being used in mocks. As for PHP, I'm sorry I have no idea on what tool to use for mocks and how you use them
Related
I need to mock CurrencyEnum by overload it, but it's not the end becouse i need to add interface to this mock.
This doesn't work:
Mockery::mock('overload:'.CurrencyEnum::class);
Error: (..) must be an instance of \BaseCurrency, instance of \CurrencyEnum given.
I looked at Mockery\Container::mock and I dont't have idea how to do it.
In example I want to test TestingClass::first() method
class CurrencyEnum implements BaseCurrency
{
/* methods */
}
class TestingClass
{
public function first(string $currencySymbol)
{
$abc = 'some_string';
return $this->second($abc, new CurrencyEnum($currencySymbol));
}
private function second(string $abc, BaseCurrency $currency)
{
/* code */
}
}
The overload method works by intercepting the autoload mechanism: it registers an autoloader for the overloaded class, loading the mocked version of the class instead of the original.
By default, it does not add many things to the mocked class. You can, however, configure just about anything you may need.
Usually, implementing one or more interfaces can be done by providing a comma-separated list of fully qualified names, the first one being the class:
$mock = Mockery::mock('MyClass, MyInterface, OtherInterface');
Due to the way that the Mockery::mock method is set up, this will not work. (The author apologises in the source code)
However, we can pass the interface(s) as second argument to the mock method:
Mockery::mock('overload:'.CurrencyEnum::class, BaseCurrency::class);
This will cause the MockConfigurationBuilder to add BaseCurrency as target; since it's an interface it will make the mock implement the interface.
An alternative notation of the above would be to use the builder directly:
Mockery::mock(
(new MockConfigurationBuilder())
->setInstanceMock(true)
->setName(CurrencyEnum::class)
->addTarget('stdClass')
->addTarget(BaseCurrency::class)
)
Having said that, it's a notoriously bad practice to mock things like enums and value objects. Why not just use the actual CurrencyEnum? Something as simple as a currency code does not quite warrant mocking at all. There's probably a structural improvement to make, which would simultaneously add tons of value to your tests and make them simpler to read.
We have some legacy laravel projects which use facades in the classes.
use Cache;
LegacyClass
{
public function cacheFunctionOne()
{
$result = Cache::someFunction('parameter');
// logic to manipulate result
return $result;
}
public function cacheFunctionTwo()
{
$result = Cache::someFunction('parameter');
// different logic to manipulate result
return $result;
}
}
Our more recent projects use dependency injection of the underlying laravel classes that the facades represent as has been hinted at by Taylor Otwell himself. (We use constructor injection for each class, but to keep the example short, here I use method injection and use a single class.)
use Illuminate\Cache\Repository as Cache;
ModernClass
{
public function cacheFunctionOne(Cache $cache)
{
$result = $cache->someFunction('parameter');
// logic to manipulate result
return $result;
}
public function cacheFunctionTwo(Cache $cache)
{
$result = $cache->someFunction('parameter');
// different logic to manipulate result
return $result;
}
}
I know facades can be mocked
public function testExample()
{
Cache::shouldReceive('get')
->once()
->with('key')
->andReturn('value');
$this->visit('/users')->see('value');
}
Which works nicely for unit tests. The problem I am trying to understand is if these facades are mocked 'globally'.
For example, lets imagine I am writing an integration test (testing a few interconnected classes while mocking services - not an end to end test using live services) which at some point, executes two separate classes which contain the same facade that calls the same method with the same parameters.
In between these classes being called, is some complex functionality that changes what data is returned by that facades method using the same parameter.*
$modernClass->cacheFunctionOne($cache); // easily mocked
// logic that changes data returned by laravel Cache object function 'someFunction'
$modernClass->cacheFunctionTwo($cache); // easily mocked with a different mock
Our modern classes are easy to test because the underlying class that the facade represents is injected into each class (in this example, each method). This means I can create two separate mocks and inject them into each class (method) to mock the different results.
$legacyClass->cacheFunctionOne();
// logic that changes data returned by laravel Cache object function 'someFunction'
$legacyClass->cacheFunctionTwo();
In the legacy systems though, it would seem that the mocked facade is 'global' so that when the facade is run in each class, the exact same value is returned.
Am I correct in thinking this?
*I understand this example may seem completely redundant from a code architecture and testing point of view, but I am stripping out all real functionality to try and give some sort of 'simple' example of what I am asking.
Dependency Injection vs Facades
One of the major benefits of Dependency Injection is that code becomes a lot more testable once you start injecting dependencies into methods instead of instantiating/hardcoding them inside the method. This is because you can pass in the dependencies from inside unit tests and they will propagate through the code.
See: http://slashnode.com/dependency-injection/
Dependency Injection stands in stark contrast to Facades. Facades are static global classes, the PHP language does not allow one to overwrite or replace static functions on static classes. The Laravel facades use Mockery to provide mock functionality and they are limited by the same facts as above.
The issue for integration testing can come where you are hoping to retrieve data from a non-mocked Cache but once you use Facade::shouldReceive() then Facade::get() will be overridden by the mocked Cache. The reverse is also true. As a result, Facades are inappropriate where you are interleaving calls for mocked and unmocked data.
In order to test your code with the different data sets that you require, the best practice would be to refactor your legacy code to use DI.
Integration Tests
Easier method
An alternative is to call multiple Facade::shouldReceive() with expectations at the beginning of your integration test. Ensuring that you have the right numbers of expectations in the right order for each of the calls you will make in the integration test. This would probably be the faster way to write tests given your existing codebase.
Harder method
Whilst dependency injection is programming best practice. It could very well be that your codebase has so many legacy classes that it would take an unbelievable amount of time to refactor. In this case, it might be worthwhile considering end-to-end integration tests using a test database with fixtures.
Appendix:
For how mockery is called by Facade see - function createMockByName(): https://github.com/laravel/framework/blob/5.3/src/Illuminate/Support/Facades/Facade.php
For examples on chaining mockery calls see fhinkel commented on Feb 6, 2015: https://github.com/padraic/mockery/issues/401
I have a method with some logic in it and I'm not sure how to unit test it. Because it's a unit test for this specific method it should run without connecting to the database. I read about stubs and mockups but I can't find a way to apply them to this situation.
I would like to force the Client:GetClient to return the client object with the right properties so I can test each logic branch.
class ClientType {
function GetClientType($id) {
$objClient = Client::GetClient($id);
if ($objClient->Returning == 1) {
return 'returning';
}
else {
return 'normal';
}
}
This is the test I had in mind
class ResourceTest extends PHPUnit_Framework_TestCase {
function testGetClientType() {
$objClientType = new ClientType();
$this->assertTrue($objClientType->GetClientType(100), 'normal');
}
}
The problem is the dependency $objClient = Client::GetClient($id); The GetClient will pull a client from database but I need to replace this with a Stub so the unit tests work without real access to the database.
Conclusion
If you have code like the one presented: refactor it and use Dependency Injection.
If you have legacy code or just don't want to refactor try this solution: http://sebastian-bergmann.de/archives/885-Stubbing-Hard-Coded-Dependencies.html
With PHPUnit you can do
$class = $this->getMockClass(
'Client', /* name of class to mock */
array('getClient') /* list of methods to mock */
);
$class::staticExpects($this->any())
->method('getClient')
->will($this->returnValue('foo'));
In general, you want to avoid static methods though:
http://sebastian-bergmann.de/archives/883-Stubbing-and-Mocking-Static-Methods.html
https://kore-nordmann.de/blog/0103_static_considered_harmful.html
EDIT after update
PHPUnit can also stub hardcoded dependencies. See
Stubbing Hard-Coded Dependencies by Sebastian Bergmann
However, since you already noticed by now that it is a Pain the Behind to test statics and hardcoded dependencies, I suggest you remove the hardcoded dependency and static call with a real object that you inject into your ClientType instead.
Another option would be to use http://antecedent.github.io/patchwork (not affiliated with it), which
is a PHP library that makes it possible to redefine user-defined functions and methods
at runtime, loosely replicating the functionality runkit_function_redefine in pure PHP 5.3 code,
which, among other things, enables you to replace static and private methods with test doubles.
Try Mocking for such kind of a problem. I do not know PHP, but we do mocking in Java and C# in such a scenario.
EDIT
I'm sorry I didn't see that you were facing a problem with mocks. Well usually for Mocks you tell them what you expect from them and they respond to the same. LSV principle is being used in mocks. As for PHP, I'm sorry I have no idea on what tool to use for mocks and how you use them
This could be a misunderstanding on my side about mocking. I would really appreciate an explanation regarding why mocking a nonexistent or existent class is a good thing?
Example Scenario: Say we have one class dependent on the output of the second class, and we change the output format or whatever. Wouldn't this keep our tests succeeding although they're outdated in the first class?
The answer is yes ! If you respect some testability principles in your code, you can isolate one method from others in the same class. That's the goal of unit testing.
See this example :
<?php
class User {
public function years()
{
return floor($this->months() / 12);
}
public function months()
{
// Database call or anything else, it's a black box !
}
}
class UserTest extends TestCase {
public function testYearsReturnTheNumberOfYears()
{
$user = \Mockery::mock('User[months]');
$user->shouldReceive('months')->andReturn(18);
assertEquals(1, $user->years());
}
}
The test will pass even if the method months is broken. Read the mockery documentation for more explanations.
Mocking is the key of unit testing. Without this tool, when something is broken in you code, all of your tests fail and you can't find the origin of the error. PHP is not enough flexible to mock everything like in Ruby or Javascript but with good design patterns you can do a great job.
A very good example is Laravel where all the core classes could be mocked. Like that, you could simulate a mistake in a database call or an email sending.
I was having similar problem. Where I had to mock same class methods with dependency injection. So partial mocks with Mockery...
$mock = Mockery::mock('MyClass[methodToMock1, methodToMock2]', array('constructor parameter 1', 'constructor parameter 2'));
$mock->shouldReceive('methodToMock1')->once()->andReturn('someValue');
$mock->shouldReceive('methodToMock2')->once()->andReturn('someValue2');
$this->assertTrue($mock->methodNum3(), 'some assert');
Hope this will help someone.
I am trying to test a class that manages data access in the database (you know, CRUD, essentially). The DB library we're using happens to have an API wherein you first get the table object by a static call:
function getFoo($id) {
$MyTableRepresentation = DB_DataObject::factory("mytable");
$MyTableRepresentation->get($id);
... do some stuff
return $somedata
}
...you get the idea.
We're trying to test this method, but mocking the DataObject stuff so that (a) we don't need an actual db connection for the test, and (b) we don't even need to include the DB_DataObject lib for the test.
However, in PHPUnit I can't seem to get $this->getMock() to appropriately set up a static call. I have...
$DB_DataObject = $this->getMock('DB_DataObject', array('factory'));
...but the test still says unknown method "factory". I know it's creating the object, because before it said it couldn't find DB_DataObject. Now it can. But, no method?
What I really want to do is to have two mock objects, one for the table object returned as well. So, not only do I need to specify that factory is a static call, but also that it returns some specified other mock object that I've already set up.
I should mention as a caveat that I did this in SimpleTest a while ago (can't find the code) and it worked fine.
What gives?
[UPDATE]
I am starting to grasp that it has something to do with expects()
I agree with both of you that it would be better not to use a static call. However, I guess I forgot to mention that DB_DataObject is a third party library, and the static call is their best practice for their code usage, not ours. There are other ways to use their objects that involve constructing the returned object directly. It just leaves those darned include/require statements in whatever class file is using that DB_DO class. That sucks because the tests will break (or just not be isolated) if you're meanwhile trying to mock a class of the same name in your test--at least I think.
When you cannot alter the library, alter your access of it. Refactor all calls to DB_DataObject::factory() to an instance method in your code:
function getFoo($id) {
$MyTableRepresentation = $this->getTable("mytable");
$MyTableRepresentation->get($id);
... do some stuff
return $somedata
}
function getTable($table) {
return DB_DataObject::factory($table);
}
Now you can use a partial mock of the class you're testing and have getTable() return a mock table object.
function testMyTable() {
$dao = $this->getMock('MyTableDao', array('getMock'));
$table = $this->getMock('DB_DataObject', ...);
$dao->expects($this->any())
->method('getTable')
->with('mytable')
->will($this->returnValue($table));
$table->expects...
...test...
}
This is a good example of a dependency in your code - the design has made it impossible to inject in a Mock rather than the real class.
My first suggestion would be to try and refactor the code to use an instance rather than a static call.
What's missing (or not?) from your DB_DataObject class is a setter to pass a prepared db object before calling the factory method. That way you can pass a mock or a custom db object (with the same interface) should the need arise.
In your test setup:
public function setUp() {
$mockDb = new MockDb();
DB_DataObject::setAdapter($mockDb);
}
The factory() method should return the mocked DB instance. If it's not already integrated into your class, you will probably have to refactor the factory() method as well to make it work.
Are you require/including the class file for DB_DataObject in your test case? If the class doesn't exist before PHPUnit tries to mock the object you can get errors like this.
With PHPUnit MockFunction extension plus runkit you can also mock static methods. Be careful, because it's monkey patching and therefore should only be used in extreme cases. Does not substitute good programming practices.
https://github.com/tcz/phpunit-mockfunction