Search code examples
unit-testingsymfonycontainersfunctional-testing

Symfony2: Mocked service is set in the container but not used by the controller (it still uses the original service)


I'm writing the functional tests for a controller. It uses a class to import some data from third party websites and to do this I wrote a class that I use into Symfony setting it a service.

Now, in my functional tests, I want to substitute this service with a mocked one, set it in the container and use it in my functional tests.

So my code is the following:

    // Mock the ImportDataManager and substitute it in the services container
    $mockDataImportManager = $this->getMockBuilder('\AppBundle\Manager\DataImportManager')->disableOriginalConstructor()->getMock();
    $client->getContainer()->set('shq.manager.DataImport', $mockDataImportManager);

    $client->submit($form);
    $crawler = $client->followRedirect();

As I know that between each request the client reboots the kernel and I have to set again the mocked class, I set the mock immediately before the calling to $client->submit.

But this approach seems not working for me and the controller still continue to use the real version of the service instead of the mocked one.

How can I use the mocked class to avoid to call the remote website during my functional test?

If I dump the set mocked service, I can see it is correctly set:

dump($client->getContainer()->get('shq.manager.DataImport'));die;

returns

.SetUpControllerTest.php on line 145:
Mock_DataImportManager_d2bab1e7 {#4807
  -__phpunit_invocationMocker: null
  -__phpunit_originalObject: null
  -em: null
  -remotes: null
  -tokenGenerator: null
  -passwordEncoder: null
  -userManager: null
}

But it is not used during the $form->submit($form) call and, instead, is used the original service.

UPDATE Continuing searching for a solution, I landed on this GitHub page from the Symfony project, where a user asks for a solution to my same problem. The second call doesn't use the mocked/substituted version of his class, but, instead, the original one.

Is this the correct behavior? So, is it true that I cannot modify the service container on a second call to the client?


Solution

  • Yet, I don't understand why the service is not substituted in the container and I haven't a real solution to that problem.

    Anyway I found some sort of workaround, in reality more correct as solution (also if it remains unclear why the service is not substituted and this is a curiosity I'd like to solve - maybe because the $client->submit() method uses the POST method?).

    My workaround is a simple test double.

    I create a new class in AppBundle/Tests/TestDouble and called it DataImportManagerTestDouble.php.

    It contains the unique method used by the controller:

    namespace AppBundle\Tests\TestDouble;
    
    use AppBundle\Entity\User;
    
    class DataImportManagerTestDouble
    {
        public function importData(User $user)
        {
            return true;
        }
    }
    

    Then, I instantiate it in the config_test.yml (app/config/config_test.yml) file in the following way:

    services:
        shq.manager.DataImport:
            class: AppBundle\Tests\TestDouble\DataImportManagerTestDouble
    

    This way, during the tests, and only during the tests, the class loaded as service is the TestDouble and not the original one. So the test pass and I'm (relatively) happy. For the moment, at least.