Skip to content

Injecting dependencies and using Phpunit mocks after 3.2 deprecation #23311

Closed
@emil-nasso

Description

@emil-nasso
Q A
Bug report? no
Feature request? yes
BC Break report? no
RFC? no
Symfony version >3.2.0

With 3.2 this kind of approach to functional testing and mocking was deprecated:

https://stackoverflow.com/questions/19726281/how-to-mock-symfony-2-service-in-a-functional-test

There is currently no clear replacement for this that doesn't require a lot of rewrites.

Take this example:

You have an application that lists products. It consists of a controller, a product repository, and a database connection. The repository uses the database connection to list products.

We would like to write some functional tests that test two scenarios: there are no products and there are products. We already have unit tests for our repository with a mocked database connection/test-database. We need to change the behavior of the repository on a per-test basis.

We have two options, as I see it, unit testing the controller and replacing the class in the test environments config file.

If we have all our controllers registered as services, inject all dependencies into them and don't access the container from the controller we can simply create an instance of the controller with phpunit mocked dependencies and unit test it. This is not a fully functional test and symfony is never booted. No events will be run, for example.

The other option is to create a RepositoryMock class that extends the Repository or implements a common interface and has a setData() method and overrides the other methods in the original class. With this approach we can use $client = self::createClient(); and $client->getContainer()->get('repository')->setData(...) in the test before making the request. This takes some work as you have to manually mock all classes and figure out a way to change their behavior with a call to public methods. We can't use phpunit mocks here as it is not possible to replace the entire object in the container.

There might be a third option, that is what I'm trying to get to here. :)

The issue with the method that was deprecated is the at that stage in the execution there is no guarantee that the object has not already been used. It would be nice if you could replace objects in the container configuration when creating the container before any of the objects have been used.

Take a look at this example:

<?php
namespace Tests\AppBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class PostControllerTest extends WebTestCase
{
    public function testShowPost()
    {
        $mock = /** phpunit mocked version of the service with data and asserts relevant to this test**/
        $client = static::createClient([
            'services' => [
                'repository' => $mock    
            ]
        ]);

        $crawler = $client->request('GET', '/post/hello-world');

        $this->assertGreaterThan(
            0,
            $crawler->filter('html:contains("Hello World")')->count()
        );
    }
}

The createClient method takes an options array. We pass in the services we want to override to it so that it can be passed along when booting the kernel and can be loaded when the container is created.

Would this be possible? Are there any issues with this that I'm overlooking? I think this approach could potentially make testing easier and cleaner. :)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions