PHPUnit testing with closures - php

This came up trying to write a test for a method of a class that calls a mock method with a closure. How would you verify the closure being called?
I know that you would be able to assert that the parameter is an instance of Closure. But how would you check anything about the closure?
For example how would you verify the function that is passed:
class SUT {
public function foo($bar) {
$someFunction = function() { echo "I am an anonymous function"; };
$bar->baz($someFunction);
}
}
class SUTTest extends PHPUnit_Framework_TestCase {
public function testFoo() {
$mockBar = $this->getMockBuilder('Bar')
->setMethods(array('baz'))
->getMock();
$mockBar->expects($this->once())
->method('baz')
->with( /** WHAT WOULD I ASSERT HERE? **/);
$sut = new SUT();
$sut->foo($mockBar);
}
}
You can't compare two closures in PHP. Is there a way in PHPUnit to execute the parameter passed in or in some way verify it?

If you want to mock an anonymous function (callback) you can mock a class with __invoke method. For example:
$shouldBeCalled = $this->getMock(\stdClass::class, ['__invoke']);
$shouldBeCalled->expects($this->once())
->method('__invoke');
$someServiceYouAreTesting->testedMethod($shouldBeCalled);
If you are using latest PHPUnit, you would have to use mock builder to do the trick:
$shouldBeCalled = $this->getMockBuilder(\stdClass::class)
->setMethods(['__invoke'])
->getMock();
$shouldBeCalled->expects($this->once())
->method('__invoke');
$someServiceYouAreTesting->testedMethod($shouldBeCalled);
You can also set expectations for method arguments or set a returning value, just the same way you would do it for any other method:
$shouldBeCalled->expects($this->once())
->method('__invoke')
->with($this->equalTo(5))
->willReturn(15);

Your problem is that you aren't injecting your dependency (the closure), which always makes unit testing harder, and can make isolation impossible.
Inject the closure into SUT::foo() instead of creating it inside there and you'll find testing much easier.
Here is how I would design the method (bearing in mind that I know nothing about your real code, so this may or may not be practical for you):
class SUT
{
public function foo($bar, $someFunction)
{
$bar->baz($someFunction);
}
}
class SUTTest extends PHPUnit_Framework_TestCase
{
public function testFoo()
{
$someFunction = function() {};
$mockBar = $this->getMockBuilder('Bar')
->setMethods(array('baz'))
->getMock();
$mockBar->expects($this->once())
->method('baz')
->with($someFunction);
$sut = new SUT();
$sut->foo($mockBar, $someFunction);
}
}

If the closure has some side effects inside SUT that could be verified by the test after the mock invocation, use returnCallback to provide another closure to be called with the passed arguments and have its return value returned to SUT. This will allow you to call SUT's closure to cause the side effects.
class SUT {
public function foo($bar) {
$someFunction = function() { return 5 * 3; };
return $bar->baz($someFunction);
}
}
class SUTTest extends PHPUnit_Framework_TestCase {
public function testFoo() {
$mockBar = $this->getMockBuilder('Bar')
->setMethods(array('baz'))
->getMock();
$mockBar->expects($this->once())
->method('baz')
->will($this->returnCallback(function ($someFunction) {
return $someFunction();
}));
$sut = new SUT();
self::assertEquals(15, $sut->foo($mockBar));
}
}

Related

Mock an object created in the middle of a method

I know that creating an instance of a Class in the middle of a method it's a bad practice since it makes code hard to test.
But I can't refactor the code, so I need to find a way to mock an Object created with new in the middle of a method under test.
Used Frameworks: PHPUnit, Mockery, WP_Mock
Example: Here I need to mock the get_second_string() method from the instance of the class ExternalClass
Class MyClass {
function methodUnderTest($string) {
$objToMock = new ExternalClass();
$second_string = $objToMock->get_second_string();
$final_string = $string . $second_string;
return $final_string;
}
}
Class TestMyClass extends PHPUnit_Framework_TestCase {
public function setUp() {
}
public function tearDown() {
}
public function test_methodUnderTest() {
$externalObject = $this->getMockBuilder('ExternalClass')
->setMethods(['get_second_string'])
->getMock;
$externalObject->expects($this->once())
->method('get_second_string')
->willReturn(' two');
$testObj = new MyClass();
$this->assertEquals('one two', $testObj->methodUnderTest('one');
}
}
If you really have no opportunity to refactor the code or do some appropriate integration testing, you might want to take a look at https://github.com/php-test-helpers/php-test-helpers#intercepting-object-creation and https://github.com/krakjoe/uopz/tree/PHP5
Still I think the code you make would profit a lot more from refactoring than monkey patching.
Besides, the refactoring does not need to be very heavy. You might do at least this:
class MyClass
{
private $externalsFactory;
public function __construct($externalsFactory){
$this->externalsFactory = $externalsFactory;
}
public function methodUnderTest($str){
$external = $this->externalsFactory->make();
$second_string = $external->get_second_string();
$finalString = $str.$second_string;
return $finalString;
}
}
class ExternalsFactory
{
public function make(){
return new ExternalClass();
}
}
class ExternalClass
{
public function get_second_string(){
return 'some real stuff may be even from database or whatever else it could be';
}
}
class MyClassTest extends PHPUnit_Framework_TestCase
{
private $factoryMock;
private $myClass;
public function setUp(){
$this->factoryMock = $this->getMockBuilder('ExternalsFactory')
->getMock();
$this->myClass = new MyClass($this->factoryMock);
}
public function testMethodUnderTest(){
$extenalMock = $this->createMock('ExternalClass');
$extenalMock->method('get_second_string')
->willReturn('second');
$this->factoryMock->method('make')
->willReturn($extenalMock);
$this->assertSame('first-and-second', $this->myClass->methodUnderTest('first-and-'));
}
}
IMHO there is no way to do such a thing. You should pass the object as parameter to the method.
You can not mock entire object.
But with phpunit you can do something like this:
$f = $this->getMockBuilder(<your_class>)->disableOriginalConstructor()
->setMethods(array(
<mocked_method_1>, <mocked_method_2>
))->getMock();
This way, newly created object ommits constructor and you specify which method are going to behave normally and which you mock.
in testing you can specify what the method/s will return, like this:
$f->method(<mocked_method_1>)->willReturn(<dummy_data>);
using this, you will not test the mocked object in any way, but can test method which is creating the object..

Mockery forgetting byDefault setup when using shouldReceive for the same method but different arguments

We are experimenting a strange behaviour of Mockery (0.9.2) while tdd-ing a Symfony controller which makes use of several request parameters grabbed using the Request service. We are using PHPUnit (3.7) as testing framework.
The way we approach TDD is using setUp method for creating mocks and configuring them by using byDefault() so they can provide a neutral happy-flow scenario. Then, in each test method, we become specific about our expectations about mock behaviour.
I isolated the problem in a proof of concept test just for making the analysis easier. Here we go.
This is the test class itself:
class FooTest extends \PHPUnit_Framework_TestCase
{
private $request;
public function setUp()
{
$this->request = \Mockery::mock('Symfony\Component\HttpFoundation\Request');
$this->request->shouldReceive('get')->with('a')->andReturnNull()->byDefault();
$this->request->shouldReceive('get')->with('b')->andReturnNull()->byDefault();
}
public function test_bar_checks_request_a_parameter()
{
$this->request->shouldReceive('get')->with('a')->andReturn('a')->once();
$foo = new Foo($this->request);
$foo->bar();
}
}
And this is the tested class:
use Symfony\Component\HttpFoundation\Request;
class Foo
{
private $request;
function __construct(Request $request)
{
$this->request = $request;
}
public function bar()
{
$a = $this->request->get('a');
$b = $this->request->get('b');
}
}
In the test test_bar_checks_request_a_parameter I would expect bar() method to get 'a' when calling get('a') on the request mock while getting null when calling get('b') instead.
But, instead, we are getting this error:
No matching handler found for Mockery_0_Symfony_Component_HttpFoundation_Request::get("b").
Which seems to say that the Request mock forgot the setUp we made for get('b') call
shouldReceive('get')->with('b')->andReturnNull()->byDefault()
Is this a Mockery limitation? Is a bad approach on our side, a test smell maybe?
Thanks in advance
It's a Mockery limitation. When you set a new expectation for a method, Mockery disables all the byDefault() expectations for that method, even if they were set with distinct arguments.
There is an open issue regarding that:
https://github.com/padraic/mockery/issues/353
You can solve this by using an array of values and a function that will compute the return value each time. The trick is making the array accesible from the test methods so you can change the return values:
class FooTest extends \PHPUnit_Framework_TestCase
{
private $request;
private $get_return_values = array();
public function setUp()
{
$this->request = \Mockery::mock('Symfony\Component\HttpFoundation\Request');
$this->request->shouldReceive('get')->andReturnUsing(function($arg) {
return isset($this->get_return_values[$arg]) ? $this->get_return_values[$arg] : null;
});
}
public function test_bar_checks_request_a_parameter()
{
$this->get_return_values['a'] = 'a';
$foo = new Foo($this->request);
$foo->bar();
}
}

How to reset expectations on PHPUnit mock object

I have a large amount of tests in a TestCase. I want to set up a mock object that returns the same value in most of the tests, but in a few of the tests I would like to customize that value.
My idea was to create a set_up() method (I was unable to set expectations inside the automatically invoked setUp()), and to manually call it at the beginning of each test. In this method, I would set the default return value, then in the few tests that need to customize the return value, I would call expects a second time, and hopefully overwrite the default return value. This does not work, the return value is not overwritten.
Here is a simplified example:
<?php
class SomeClass {
function someMethod() {
}
}
class SomeTest extends PHPUnit_Framework_TestCase {
private $mock;
function set_up() {
$this->mock = $this->getMockBuilder('SomeClass')
->disableOriginalConstructor() // This is necessary in actual program
->getMock();
$this->mock->expects($this->any())
->method('someMethod')
->will($this->returnValue(1));
}
function test() {
$this->set_up();
$this->mock->expects($this->any())
->method('someMethod')
->will($this->returnValue(2));
$this->assertEquals(2, $this->mock->someMethod());
}
}
It seems that this should be possible from reading How to reset a Mock Object with PHPUnit.
PHPUnit mock with multiple expects() calls does not answer my question.
I am using phpUnit 4.2
You can pass arguments to set_up method so it can configure the mock as needed:
function set_up($someMethodReturnValue = 1) {
$mock = $this->getMockBuilder('SomeClass')
->disableOriginalConstructor() // This is necessary in actual program
->getMock();
$mock->expects($this->any())
->method('someMethod')
->will($this->returnValue($someMethodReturnValue));
return $mock;
}
function test() {
$mock = $this->set_up(2);
$this->assertEquals(2, $this->mock->someMethod());
}
You can further enhance the set_up() method. Eventually you could create a Mock creation class if there are lots of options.

Unable to test static method using Mock class in PHPUnit

I'm trying to test a static method on a class, but it relies on a static array property that doesn't get reset between tests, so I though I could create a mock class & test against that.
The only problem is that the mock class static method is not returning the value as the original static method does.
Here's my class...
class Thing {
static public function doThing() {
return 'yeah!';
}
}
... and here's be test class...
class ThingTest {
public function testDoSomething() {
$mock_class = $this->getMockClass('Thing');
$this->assertEqual('yeah!', $mock_class::doThing())
}
}
This test fails with the message "Failed asserting that null matches expected 'yeah!'."
What am I missing here? I thought not specifying the methods on the getMock() call meant that the original methods carried over, but clearly not. :o(
Any pointers would be great. Thanks.
Edit
I typed this up from memory, rather than from copying code, as I'd made further change to try to resolve this. After writing more tests for mock objects I realised that if you pass array() as the second parameter ($methods) to getMock[Class] then it stubbed out all the methods, & I believe this is what I had actually done. I was doing this, as I also wanted to pass the constructor parameter in the 3rd argument. The code probably looked more like this...
class ThingTest {
public function testDoSomething() {
$mock_class = $this->getMockClass(
'Thing',
array(),
array( 'something' )
);
$this->assertEqual('yeah!', $mock_class::doThing())
}
}
Maybe $mock_class is an object, not a string class name? Try to use next:
class ThingTest {
public function testDoSomething() {
$mock_class = $this->getMockClass('Thing');
$this->assertEqual('yeah!', Thing::doThing())
}
}
Is this work?
You forgot extending by PHPUnit_Framework_TestCase.
class ThingTest extends PHPUnit_Framework_TestCase {
public function testDoSomething() {
$mock_class = $this->getMockClass('Thing');
$this->assertEqual('yeah!', $mock_class::doThing())
}
}
You shouldn't mock the class containing the method you want to test. If you mock the class, you can't call any method either before you haven't defined it via
-> expects('methodName')
-> with($this->equalTo($parameter),[$this->equalTo($parameter2),...])
-> will($this->returnValue($value))
. Mocking is used to reduce complexity of a test. Let's say you want to test a function that looks like this:
class test{
private $someClass;
public function __construct(SomeInterface $someClass){
$this -> someClass = $someClass;
}
public function test(){
$someVariable = $this -> someClass -> doSomething();
changeSomething($someVariable);
return $someVariable;
}
}
If you don't mock someClass, you'd have to test the whole code of someClass -> doSomething. But that is not what you want to do. You just want to use the return of the function doSomething. That is the point where you need a mock. It reduces the complexity of your test and offers REAL UNIT tests instead of testing the whole app.
So what you need to do is:
class ThingTest {
public function testDoSomething() {
$class = new Thing();
$this->assertEquals('yeah!', $class::doThing());
}
}
There is no need for a mock.

PHPUnit: Use Mock to modify return value?

I would like to have a PHPUnit Mock which executes a method like normal, but then modifies the return value in some way before the function returns.
What I have
I have a set of derived classes, similar to below:
abstract class Base
{
abstract protected function getUrl();
public function callUrl() {
$url = $this->getUrl();
// some code to call the URL here
}
}
class Foo extends Base
{
protected function getUrl() {
return "http://www.example.com/Foo";
}
}
class Bar extends Base
{
protected function getUrl() {
return "http://www.example.com/Bar";
}
}
Please note the classes I have are much more complex, and some of the items I have to test have side-effects (such as writing to a database, etc).
The naive, duplicate code approach
If I only had a single derived class (eg; Foo), then I could do the following:
class FooMock extends Foo
{
protected function getUrl() {
return parent::getUrl() . "?sandbox";
}
}
class theTest extends PHPUnit_Framework_TestCase
{
public function testIt() {
$mock = new FooMock();
// assert something
}
}
Unfortunately, this means I would need a specific "Mock" class for each derived class I want to test, all of which perform exactly the same function.
The preferred approach
Instead, I would like to be able to do something like the following:
function callback ($returnValue) {
return $returnValue . "?sandbox";
}
class theTest extends PHPUnit_Framework_TestCase
{
private $mock;
public function testFoo() {
$this->mock = $this->getMockBuilder('Foo')->getMock();
$this->setupMock();
// assert something
}
public function testBar() {
$this->mock = $this->getMockBuilder('Bar')->getMock();
$this->setupMock();
// assert something
}
public function setupMock() {
$this->mock->expects($this->any())
->method('getUrl')
->will($this->postProcessReturnValue('callback'));
}
}
Is this at all possible with PHPUnit?
Update: It was suggested I have an instance of the original class, and an instance of the mock class. Use the original class to get the original return value and modify that. This modified value is then used as the return for the Mock. This is not a feasible way to go about things as the classes are more complex (they have side effects such as writing to the DB).
An example where this would not work;
class Foo extends Base
{
$id = 0;
public function saveToDB() {
$this->id = saveToDBAndReturnId();
}
protected function getUrl() {
if ($this->id > 0) {
return "http://www.example.com/".$this->id;
}
throw new Exception("No ID");
}
}
$foo = new Foo();
$foo->saveToDB();
$url = $foo->getUrl();
Obviously the returned URL would be different between multiple calls. I could always mock saveToDB, but that's starting to feel dirty when all I want to do is post-process the result of getUrl.
PHPUnit allows you to define a stub method that will use a callback to determine what to return.
$this->mock->expects($this->any())
->method('getUrl')
->will($this->returnCallback('callback'));
You can define your callback to call the original class method and modify the return value.
Of course, using mock objects in this way more or less defeats the purpose of having them be "mock" objects, since the mock objects will now rely on the underlying implementation.

Categories