I have a love/hate relationship with codeception. When it works, it’s my prince charming on a white horse coming to rescue me from the plight of testing. But ever so often it’s a convoluted and badly documented mess that wrecks havoc to my productivity as I frantically search the net for the one blog post that could help me out. Maybe this can be one those blog posts for somebody else. It’s about how to replace a service (i.e. with a mock) inside the symfony container in a functional test.
Say you have an external service that you call to obtain some data and in one particular case you don’t want to invoke the real service but redirect all calls to a mock that gives a predefined answer, then you need to replace this service inside the symfony container. This is such a common task for me, that I am baffled that there is no $I->replaceServiceWithMock($serviceName, $mockService)
method to call. But we can make our own!
Go to your tests folder. There you’ll find _support/Helper/Functional.php
. Now open up that file and behold:
// here you can define custom actions
// all public methods declared in helper class will be available in $I
Neat. So I did this:
|
|
Cool. Now let’s see that in action. Maybe you’re testing a service, that has to interact with an external API and you can’t have your test make a real call to that API because that would have real life consequences like ordering 10,000 roles of toilet paper. You could do this:
|
|
That’s it. If you step through your test with xdebug, you can see that the service has been replaced with your mock. Now you can formulate your expectations and be safe that you don’t accidentally flood your real API with test data.
Update: Unfortunately, it’s not that easy. This approach only works, if you want to test a single service and grab that service from the container the same time that you submit your mock to the container. My example works fine, but as soon as I try to mock a service, that was used in another service, that is used in a controller, that I want to test via the codeception FunctionalTester-methods, the unmocked service pops up again, like a zombie and bites down. Hard. So, what you have to do, is to persist the service in the container:
|
|
To be honest, I am not sure if the second parameter for persistService should be true or false, and the documentation does not help because it doesn’t matter if I set this parameter to true or false, the service is permanently mocked. Which sucks if you happen to run another test directly afterwards and for some reasons you don’t want to use this mock this time. Like me. In order to do that, you have to restore the original service in the container after your done with your tests. I assumed that would happen anyway or that’s what the second parameter is for, but nope. I had to write the antidote for the replaceServiceWithMock method:
|
|
Now, all that’s left is to add an _after-method to the cest and we are done:
|
|