I am desperately trying to unit test a module for a shopsystem. That shop system uses static methods which I have to call in my functions I want to test.
public function toTest() {
$value = \Context::getData();
return $value;
}
Now how can I unit test that function while mocking this static call? I tried using AspectMock but that does not work because it apparently needs access to the original \Context class which is not available since it's an external system. I also tried using class_alias to create my own Context class but that does not work either because I need different Context output depending on which function I am testing. And setting class_alias multiple times for different tests does not work because the same class can't be declared multiple times and #runTestsInSeparateProcesses did not have the expected effect.
Edit: None of the duplicates provided a viable solution to my situation, so I don't think this is a duplicate. With no access to the shopsystem code and especially with hard to maintain code like that, PHP does not make it easy to unit test this. Also the solution I found should help others with similar settings.
I could solve my issue with the Mockery library. I tried out a few but nothing worked. With Mockery everything seems possible now. This link really helped: https://robertbasic.com/blog/mocking-hard-dependencies-with-mockery/
You can easily mock static calls to classes that don't belong to you:
public function methodToTest() {
return \Context::getData();
}
public function testMethodToTest() {
m::mock('alias:\Context')
->shouldReceive('getData')
->andReturn('foo');
}
And even instantiations for classes you don't have access to:
public function methodToTest() {
$obj = new \Category(5);
return $obj->id;
}
public function testMethodToTest() {
m::mock('overload:\Category')
->shouldReceive('__construct')
->with(5)
->andSet('id', 5);
}
But you have to keep in mind that you need the two phpunit annotations at the beginning of the class:
* #runTestsInSeparateProcesses
* #preserveGlobalState disabled
Related
I've been successfully using Mockery with PHPUnit tests lately. Yet, there is a dependency in a project I'm currently working that uses static method calls to interact with an API. I'm struggling to test one particular use case and it feels like I'll find other like this during the development roadmap.
Using this class as an example:
namespace Name\Space;
class User
{
/**
* #return \Name\Space\User[]
*/
public static function list(): array
{
// ...
}
public static function create(array $attrs): User
{
// ...
}
}
In case I just want to assert a method returns a primitive type, such as an array:
Mockery::mock('alias:\Name\Space\User')
->shouldReceive('list')
->andReturn([]);
It works fine, primarily because I'm not testing the array contents.
However, I have to call the create method, which returns an instance of the class itself (User). If I do something like this:
$user = new \Name\Space\User();
Mockery::mock('alias:\Name\Space\User')
->shouldReceive('create')
->andReturn($user);
The alias, obviously, won't work because the class was already loaded through the autoloader (composer's, in this case).
Does anyone have a suggestion on how to workaround this?
What about creating User in a closure?
<?php
$user = Mockery::mock('overload:\Name\Space\User')
->shouldReceive('create')
->andReturnUsing(function() {
return new \Name\Space\User();
});
Mocking static stuff is always painful.
I would recommend creating a Proxy object that is calling the static API calls and just returns the API results and inject this object everywhere you need to call the API.
This way it is easy to test by simply mocking the proxy object.
The proxy object itself can then be tested in an end to end test outside of the pure unit test scope.
You can still do more invasive stuff like this
https://www.pagemachine.de/blog/mocking-static-method-calls/?cn-reloaded=1
But writing code that doesn't belong to your unit tests purely to make something testable doesn't feel right to me.
<?php
use yii\db\ActiveRecord;
class Model extends ActiveRecord
{
protected static $ids = [];
public static function getIds()
{
if (empty(static::$ids)) {
static::$ids = static::find()->select('id')->column();
}
return static::$ids;
}
}
How to use the test to make sure that the query is executed once by repeatedly calling this method ?
Preferably using codeception or phpunit.
Tests are not just a way to ensure your code works, they also help identify code smells. In your case writing a test is hard, because you use static methods.
There used to be a staticExpects method but that was deprecated in phpunit long ago, so that's not really feasible. The best way to make this code testable is to remove the static keyword. This is easy for getIds() but since the static find() is defined by a 3rd party (yii's ActiveRecord) you can't really remove it. Instead you could wrap it in a non-static method. This gives you the benefit of being able to move away from the Active Record to some other implementation like Doctrine in the future, by just touching these small methods wrapping the 3rd party code.
Once you do this you could create a partial mock of your model to make sure that method is called:
class Model extends ActiveRecord
{
private $ids;
protected function findIds()
{
return static::find()->select('id')->column();
}
public function getIds()
{
if (empty($this->ids)) {
$this->ids = $this->findIds()
}
return $this->ids;
}
}
and in your test:
public function testFindIdsIsCalledWhenGetterIsNotInitialized()
{
$model = $this->getMockBuilder(Model::class)
->setMethods(['findIds'])
->getMock();
$model->expects($this->once())
->method('findIds')
->will($this->returnValue([1, 2, 3]));
$ids = $model->getIds();
$this->assertEquals([1, 2, 3], $ids);
}
This should have 2 assertions, one for the expected method call and one for the returned values. This test bypasses the Active Record and only ensures that your getIds() method works as expected. Another way to approach this is, as mentioned in the comments to your question, to use a functional test that actually tests the database interactions by fetching the data from a (test) database. Obviously since this requires having a database connection and retrieving test data, e.g. from some previously setup fixtures, it's a bit more work and the test will be slower. Depending on how big your project is that might not be an issue and you might feel more comfortable testing the logic in the Active Record implementation as well.
I know staticExpects is deprecated as of PHPUnit 3.8 and will be removed completely with later versions.
But in our project, using static function everywhere.So,It's a big problem to make phpunit.And In our dev,phpunit version is 4.6.6,I can not back to 3.8.
My question is how can I do like staticExpects?
code:
class A {
public static function staticfun(){
//dosomething....
}
}
class B {
public static function callA(){
A::staticfun();
}
}
class TestA extends PHPUnit_Framework_TestCase{
public function test(){
//I want to mock staticfun()
B::callA();
}
}
To do this you'd need to use an extension like uopz that allows you to redefine functions and methods at runtime.
In your test class you'd add something like:
public static function setupBeforeClass()
{
uopz_backup("A", "staticfun");
uopz_function("A", "staticfun", function () {
// do something else
});
}
public static function tearDownAfterClass()
{
uopz_restore("A", "staticfun");
}
This'll:
back up the original method
redefine it as the given closure
restore the original when the tests in the class are complete
You can, in a general case, not mock static function calls. Don't use static calls if you intend to test your software with mocks. You can try and fiddle with evil tricks, but this usually is a huge pain.
You probably didn't read the documentation for the staticExpects feature of PHPUnit thoroughly. It does not do what you need. Sebastian implemented that feature in PHPUnit 3.5, but it didn't work as he was intending, because people didn't understand its limitations. So it was removed again in 3.8.
Face the fact that you created untestable software by using static calls. Start to throw them out and improve the testability of the software that way. It is painful and will cost time, but it is the only way.
I'm not sure if it's the proper place for such question since it's rather theoretical than the specific code sample but I'll ask anyway.
So, at some point PHP introduced type constrains in function definition (except basic types of course), i.e.
class A {
public $value;
}
function foo($someInt, A $a) {...}
What make me wondering is if PHPUnit mocks can be used in such situation:
class functionTest extends PHPUnit_Framework_TestCase {
public function testFoo() {
$mockA = $this->getMockBuilder('A')->getMock();
$this->assertEquals('some result', foo(1, $mockA));
}
}
Would such call be accepted when the test runs (ofc. I skipped includes and stuff to keep it simple).
And the more interesting question: if yes, then how is it implemented?
Yes it will be working, PHPUnit will mock your object. This mocked object will dynamically extends the base Object you want to mock.
Let's say I want to test a simple helper that takes a class name as an argument and makes a redirection.
How am I supposed to test this if the function is called in many places from inside a couple of controllers? Should I test every class name that was passed as a parameter in the whole code (write them in the provider function myself)? Or is there a magical function which does that for me?
Your question is the exact reason why dependency injection -- when done correctly (not how most popular frameworks "implement" it) -- is touted as the ultimate in code testability.
To understand why, lets look at how "helper functions" and class-oriented programming make your controllers difficult to test.
class Helpers {
public static function myHelper() {
return 42;
}
}
class MyController {
public function doSomething() {
return Helpers::myHelper() + 100;
}
}
The entire point of unit testing is to verify that "units" of code work in isolation. If you can't isolate functionality, your tests are meaningless because their results could be tainted by the behavior of the other code involved. This can result in what statisticians call Type I and Type II errors: basically, this means you can get test results that might be lying to you.
In the code above, the helper cannot be easily mocked to determine that MyController::doSomething works in complete isolation from outside influences. Why not? Because we can't "mock" the behavior of the helper method to guarantee our doSomething method actually adds 100 to the helper result. We're stuck with the helper's exact behavior (returning 42). This is a problem that correct object-orientation and inversion of control eliminate entirely. Let's consider an example of how:
If MyController asks for it's dependencies instead of using the static helper function , it becomes trivial to mock the outside influences. Consider:
interface AnswerMachine {
public function getAnswer();
}
class UltimateAnswerer implements AnswerMachine {
public function getAnswer() {
return 42;
}
}
class MyController {
private $answerer;
public function __construct(AnswerMachine $answerer) {
$this->answerer = $answerer;
}
public function doSomething() {
return $this->answerer->getAnswer() + 100;
}
}
Now, it's trivially simple to test that MyController::doSomething does in fact add 100 to whatever it gets back from the answer machine:
// test file
class StubAnswerer implements AnswerMachine {
public function getAnswer() {
return 50;
}
}
$stubAnswer = new StubAnswerer();
$testController = new MyController($stubAnswerer);
assert($testController->doSomething() === 150);
This example also demonstrates how the correct use of interfaces in your code can greatly simplify the testing process. Test frameworks like PHPUnit make it very easy to mock interface definitions to perform exactly what you want them to in order to test the isolated functionality of code units.
So I hope these very simple examples demonstrate how powerful dependency injection is when it comes to testing your code. But more importantly, I hope they demonstrate why you should be wary if your framework of choice is using static (just another name for global), singletons, and helper functions.
You cannot test each possible combination of parameters to all the functions you need to test; it will take you longer than the life of the universe. So you use Human Intelligence (some might call it cheating ;-). Test it just once, in this case with a mock controller as the parameter.
Then look at your code and ask yourself if any other object passed in is really going to have it behave differently. For something you describe as a "simple helper" maybe the answer is no. But, if yes, how? Create another mock controller class that simulates that different behaviour. E.g. this second controller might not have the function your helper class expects to call. You expect an exception to be thrown. Create the unit test for that.
Repeat until satisfied.