Helpful solutions for Laravel framework developers

Partial mocking and issues you don’t expect

As we all know, writing tests is very useful when developing applications. But sometimes you might find issues when writing tests that are really hard to detect. If you ever got “Serialization of ‘Closure’ is not allowed” / “unserialize(): Error at offset 0 of 186 bytes in” errors when running PhpUnit together with Mockery – you might be interested how to solve them, if you haven’t yet – you might be interested how to avoid them in future to save your time.

Let’s create simple route:

app/Http/routes.php

1

Route::get('/service','ServiceController@test');

and simple service class:

app/Services/SumService.php

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

<?php

namespaceApp\Services;

classSumService

{

/**

* Calculates sum

*

* @return int

*/

publicfunctionsum()

{

return$this->calculate()+5;

}

/**

* Make internal calculations

*

* @return int

*/

protectedfunctioncalculate()

{

return2**3;

}

}

and really basic controller:

app/Http/Controllers/ServiceController

1

2

3

4

5

6

7

8

9

10

11

12

<?php

namespaceApp\Http\Controllers;

useApp\Services\SumService;

classServiceControllerextendsController

{

publicfunctiontest(SumService$service)

{

return$service->sum();

}

}

Now, let’s create very simple test for our controller:

tests/ExampleTest.php

1

2

3

4

5

6

7

8

9

10

<?php

classExampleTestextendsTestCase

{

publicfunctiontestBasicExample()

{

$this->get('/service');

$this->assertEquals(13,$this->response->getContent());

}

}

when we run in command line phpunit we should get green. No problem here.

Okay, but now, let’s assume that our service is something much more complicated, and we would like to mock it.

PHPUnit_Framework_Exception: [Exception]
Serialization of ‘Closure’ is not allowed

Caused by
ErrorException: unserialize(): Error at offset 0 of 186 bytes in ../vendor/phpunit/phpunit/src/Util/PHP.php:114

Luckily, when we look into laravel log, we will find cause of the error:

Method App\Services\SumService::sum() does not exist on this mock object

So it seems, that Mockery overload cannot make it as partial. Fortunately we can fix this quite easily. If we change our method into:

tests/ExampleTest.php

1

2

3

4

5

6

7

8

9

publicfunctiontestWithMockCalculate()

{

$mock=Mockery::mock('App\Services\SumService')->makePartial()

->shouldAllowMockingProtectedMethods();

$mock->shouldReceive('calculate')->once()->andReturn(15);

App::instance('App\Services\SumService',$mock);

$this->get('/service');

$this->assertEquals(20,$this->response->getContent());

}

we will get green again.

Okay, now let’s jump into the most tricky part. Let’s assume that it might happen that our controller can return also other statuses. Let’s imagine we used some route model binding or we used some middleware that will return 404 status. In our case let’s simulate this adding simple abort into our controller like so:

app/Http/Controllers/ServiceController.php

1

2

3

4

5

publicfunctiontest(SumService$service)

{

abort(404);

return$service->sum();

}

now, let’s run only the last test using:

1

phpunit--filter=testWithMockCalculate

what are we getting now? We should get again:

PHPUnit_Framework_Exception: [Exception]
Serialization of ‘Closure’ is not allowed

Caused by
ErrorException: unserialize(): Error at offset 0 of 186 bytes in ../vendor/phpunit/phpunit/src/Util/PHP.php:114

Okay, you think no problem – let’s look again into laravel log file. Well, the problem is that you won’t find anything useful there! You should get only something like this:

so it says nothing. Is it possible to track the problem? Well, if you don’t know what to look, it might be really hard. In our sample code it’s really obvious because we know what we changed in our controller and we have only a few lines of code, but what in case we use multiple services (probably with multiple mocks)? Well, in our case we could do something like this: let’s remove the comment (only temporary) from our test class we added to run each test in separate process:

tests/ExampleTest.php

1

2

3

4

/**

* @runTestsInSeparateProcesses

* @preserveGlobalState disabled

*/

and now run again only this single test (you won’t be able to run all tests as you removed this comment).

Now you should get quite standard PhpUnit failed assertion that the big HTML is not equal to expected 20.

Why the hell when having comment enabled we are getting completely useless information “Serialization of ‘Closure’ is not allowed”? To be honest – I have no idea but looking on Internet I was not the only one who had this issue. I was trying to update my 4.8.* PhpUnit to 5.4.* but the error was exactly the same. The most strange thing was that on my PC it was working really fine whereas on devserver it failed.

In that case my devserver was reaching API rate limiting so it was getting 429 response instead of expected 200 and that’s why it received this error. So the problem was exactly the same as we simulated in our test – we were expecting 200 response with some data, but we got 404 response (we added it manually in our controller just to cause the error) and the whole test was failing with very unclear message and no useful log.

So next time when you use PhpUnit separate processes and get this error I hope you will know what you should search and you will know that probably something is not working as you expect and the error you get can be fixed.