Symfony 2 functional tests with mocked services Symfony 2 functional tests with mocked services symfony symfony

Symfony 2 functional tests with mocked services


When you call self::createClient(), you get a booted instance of the Symfony2 kernel. That means, all config is parsed and loaded. When now sending a request, you let the system do it's job for the first time, right?

After the first request, you may want to check what went on, and therefore, the kernel is in a state, where the request is sent, but it's still running.

If you now run a second request, the web-architecture requires, that the kernel reboots, because it already ran a request. This reboot, in your code, is executed, when you execute a request for the second time.

If you want to boot the kernel and modify it before the request is sent to it (like you want), you have to shutdown the old kernel-instance and boot a fresh one.

You can do that by just rerunning self::createClient(). Now you again have to apply your mock, as you did the first time.

This is the modified code of your second example:

public function testAMockServiceCanBeAccessedByMultipleRequests(){    $keyName = 'testing123';    $client = static::createClient();    $client->getContainer()->set($keyName, new \stdClass());    // Check our object is still set on the container.    $this->assertEquals('stdClass', get_class($client->getContainer()->get($keyName)));    $client->request('GET', '/any/url/');    $this->assertEquals('stdClass', get_class($client->getContainer()->get($keyName)));    # addded these two lines here:    $client = static::createClient();    $client->getContainer()->set($keyName, new \stdClass());    $client->request('GET', '/any/url/');    $this->assertEquals('stdClass', get_class($client->getContainer()->get($keyName)));}

Now you may want to create a separate method, that mocks the fresh instance for you, so you don't have to copy your code ...


I thought I'd jump in here. Chrisc, I think what you want is here:

https://github.com/PolishSymfonyCommunity/SymfonyMockerContainer

I agree with your general approach, configuring this in the service container as a parameter is really not a good approach. The whole idea is to be able to mock this dynamically during individual test runs.


The behaviour you are experiencing is actually what you would experience in any real scenario, as PHP is share nothing and rebuilds the whole stack on each request. The functional test suite imitates this behaviour to not generate wrong results. One example would be doctrine, which has a ObjectCache, so you could create objects, not save them to the database and your tests would all pass because it takes the objects out of the cache all the time.

You can solve this problem in different ways:

Create a real class which is a TestDouble and emulates the results you would expect from the real API. This is actually very easy: You create a new MyApiClientTestDouble with the same signature as your normal MyApiClient, and just change the method bodies where needed.

In your service.yml, you alright might have this:

parameters:  myApiClientClass: Namespace\Of\MyApiClientservice:  myApiClient:    class: %myApiClientClass%

If this is the case, you can easily overwrite which class is taken by adding the following to your config_test.yml:

parameters:  myApiClientClass: Namespace\Of\MyApiClientTestDouble

Now the service container will use your TestDouble when testing. If both classes have the same signature, nothing more is needed. I don't know if or how this works with the DI Extras Bundle. but I guess there is a way.

Or you could create a ApiDouble, implementing a "real" API which behaves in the same way your external API does but returns test data. You would then make the URI of your API handled by the service container (e.g. setter injection) and create a parameters variable which points to the right API (the test one in case of dev or test and the real one in case of the production environment).

The third way is a bit hacky, but you can always make a private method inside your tests request which first sets up the container in the right way and then calls the client to make the request.