How to unit test active record pattern static methods? - php

I'm wondering how I would go about unit testing the active record pattern? I'm using Yii 2 for this project and I understand that normally I would want to make mocks and use dependency injection to insert these mocks into my test class - however, I don't see how I would do this with static methods since I'm not instantiating the class anywhere? For example, in Yii's active record class I would do the following to select a row from the database:
ExampleTable::find()->where(['id' => 10])->one();
Any ideas how I would mock this class that uses static methods to work directly with the database?

In Yii2 exist codeception and you can use library codeception/AspectMock to mock all static without dependency injection:
https://github.com/Codeception/AspectMock
This is example from official AspectMock manual:
Allows stubbing and mocking of static methods.
Let's redefine static methods and verify their calls at runtime.
<?php
function testTableName()
{
$this->assertEquals('users', UserModel::tableName());
$userModel = test::double('UserModel', ['tableName' => 'my_users']);
$this->assertEquals('my_users', UserModel::tableName());
$userModel->verifyInvoked('tableName');
}
I hope it will help.

Couple of ideas:
extract db related stuff to some kind of Repository/DAO class, inject it as a dependency and mock in your test
extract db related stuff to separate methods visible in tests and use partial mocking
go with tests that actually use db, so prepare your data as a part of test setup, run method and verify results based on what was returned from db -> however it may not comply with your unit test definition :)

Related

why is it impossible to test a static method with mockery or anything else

I have read in laravel's facade documentation the following sentence:
Typically, it would not be possible to mock or stub a truly static
class method.
1) question 1: I'm trying to understand facade in laravel. As I guess, it's implemented because if we have classes, and they have big namespaces and big names and every time we want to use this class and we don't want to use new keyword and use statements, we use the facade which is an easier code and readable. I also think that laravel implemented facades because they wanted to write non-static functions in their classes so that they could be tested. After all of this, we use facades like static classes (because of readability and not using new and use), but in reality, it makes new instances.
Am I right?
2) If the above is right, can you provide me an example why it's not possible to test a static method as laravel docs said?
A facade does not solve the big namespaces problem you mentioned. Big namespaces are solved using aliases. You can declare them in your config/app.php and internally Laravel will use class_alias when you call them. This is how e.g. \Cache or \DB work.
A facade is basically a proxy class to a singleton object instance of another class (the facade itself ensures the instance is a singleton).
Typically to register a singleton in Laravel you:
Add app()->singleton(ABC::class) in your service provider
Access it via app()->make(ABC::class)->...
A facade basically takes care of that for you if you haven't already registered that class as a singleton.
Basically a facade is a way to proxy that singleton instance of another class.
Also it's generally not possible to mock or stub static methods however if you are using facades you can do \ABCFacade::swap($mockObject) and therefore your facades can be mocked.
It is also false that you cannot test a static method. You can absolutely test a static method. For example:
public testStaticMethod() {
$this->assertEquals(1, ABC::method()); // We tested a static method against a desired behaviour
}
What you usually can't do is mock a static method. Here's how you would typically mock something with PHPUnit:
public testWithDependency() {
$dependency = $this->getMockBuilder(Dependency::class)->getMock();
$dependency->expects($this->once())->method('dependantMethod')->willReturn(true);
$objectToTest = new ABC($dependency); //We're passing a fake dependency which behaves in an ideal way
$this->assertEquals(1, $objectToTest->methodToTest()); //Any calls to the dependency will call mock methods and not real ones
}
The problem arises when trying to mock a static method. As you can see mocking creates mock instances of a certain type. It can't mock the static members of that type because the mock object itself is not actually of that type.
However as I just found out the statement that it's not possible to mock or stub a static method is not entirely true. There's the AspectMock you can mock static methods or helper methods. This seems to work by intercepting all function calls via a custom autoloader.
This being said, just because you can doesn't mean it's good practice to use static methods, there's other issues to consider like e.g. you normally can't have static interfaces in most programming languages or you normally can't override static methods in most programming languages. Note the "in most programming languages" part here. In PHP it's entirely possible to override static methods with late static binding but that means you need to make a conscious decision about this when implementing the static method.
Another disadvantage is that a class of statics can't implement an interface because interfaces apply to object behaviours and not the static behaviour. Therefore you can't swap out one interface for another if you are using statics which is a major disadvantage.
In general the aversion to static methods is not because of testability but because if you are coding in OOP you are really limited if you are using statics.
Hopefully this will help clear up some confusion.

Integration tests mocking facades vs injecting mocks

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

unit testing and Static methods

Reading up and picking up on unit testing, trying to make sense of the following post on that explains the hardships of static function calls.
I don't clearly understand this issue. I have always assumed static functions were a nice way of rounding up utility functions in a class. For example, I often use static functions calls to initialise, ie:
Init::loadConfig('settings.php');
Init::setErrorHandler(APP_MODE);
Init::loggingMode(APP_MODE);
// start loading app related objects ..
$app = new App();
// After reading the post, I now aim for this instead ...
$init = new Init();
$init->loadConfig('settings.php');
$init->loggingMode(APP_MODE);
// etc ...
But, the few dozen tests I had written for this class are the same. I changed nothing and they still all pass. Am I doing something wrong?
The author of the post states the following:
The basic issue with static methods is they are procedural code. I have no idea how to unit-test procedural code. Unit-testing assumes that I can instantiate a piece of my application in isolation. During the instantiation I wire the dependencies with mocks/friendlies which replace the real dependencies. With procedural programing there is nothing to “wire” since there are no objects, the code and data are separate.
Now, I understand from the post that static methods create dependencies, but don't grasp intuitively why one cannot test the return value of a static method just as easily as a regular method?
I will be avoiding static methods, but I would of liked having an idea of WHEN static methods are useful, if at all. It seems from this post static methods are just about as evil as global variables and should be avoided as much as possible.
Any additional information or links on the subject would be greatly appreciated.
Static methods themselves aren't harder to test than instance methods. The trouble arises when a method--static or otherwise--calls other static methods because you cannot isolate the method being tested. Here is a typical example method that can be difficult to test:
public function findUser($id) {
Assert::validIdentifier($id);
Log::debug("Looking for user $id"); // writes to a file
Database::connect(); // needs user, password, database info and a database
return Database::query(...); // needs a user table with data
}
What might you want to test with this method?
Passing anything other than a positive integer throws InvalidIdentifierException.
Database::query() receives the correct identifier.
A matching User is returned when found, null when not.
These requirements are simple, but you must also setup logging, connect to a database, load it with data, etc. The Database class should be solely responsible for testing that it can connect and query. The Log class should do the same for logging. findUser() should not have to deal with any of this, but it must because it depends on them.
If instead the method above made calls to instance methods on Database and Log instances, the test could pass in mock objects with scripted return values specific to the test at hand.
function testFindUserReturnsNullWhenNotFound() {
$log = $this->getMock('Log'); // ignore all logging calls
$database = $this->getMock('Database', array('connect', 'query');
$database->expects($this->once())->method('connect');
$database->expects($this->once())->method('query')
->with('<query string>', 5)
->will($this->returnValue(null));
$dao = new UserDao($log, $database);
self::assertNull($dao->findUser(5));
}
The above test will fail if findUser() neglects to call connect(), passes the wrong value for $id (5 above), or returns anything other than null. The beauty is that no database is involved, making the test quick and robust, meaning it won't fail for reasons unrelated to the test like network failure or bad sample data. It allows you to focus on what really matters: the functionality contained within findUser().
Sebastian Bergmann agrees with Misko Hevery and quotes him frequently:
Stubbing and Mocking Static Methods
Unit-Testing needs seams, seams is where we prevent the execution of normal code path and is how we achieve isolation of the class under test. Seams work through polymorphism, we override/implement class/interface and then wire the class under test differently in order to take control of the execution flow. With static methods there is nothing to override. Yes, static methods are easy to call, but if the static method calls another static method there is no way to override the called method dependency.
The main issue with static methods is that they introduce coupling, usually by hardcoding the dependency into your consuming code, making it difficult to replace them with stubs or mocks in your Unit-Tests. This violates the Open/Closed Principle and the Dependency Inversion Principle, two of the SOLID principles.
You are absolutely right that statics are considered harmful. Avoid them.
Check the links for additional information please.
Update: note that while statics are still considered harmful, the capability to stub and mock static methods has been removed as of PHPUnit 4.0
I do not see any problem when testing static methods (at least none that doesn't exists in non-static methods).
Mock objects are passed to classes under test using dependency injection.
Mock static methods can be passed to classes under test using a suitable autoloader or manipulating the include_path.
Late static binding deals with methods calling static methods in the same class.

How to unit test a method with dependencies in PHP?

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

Mock Objects in PHPUnit to emulate Static Method Calls?

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

Categories