24 votes

Tests fonctionnels Symfony 2 avec services fictifs

J'ai un contrôleur pour lequel j'aimerais créer des tests fonctionnels. Ce contrôleur effectue des requêtes HTTP vers une API externe via une interface de type MyApiClient classe. J'ai besoin de faire une simulation de cette MyApiClient afin de pouvoir tester la façon dont mon contrôleur réagit à des réponses données (par exemple, ce qu'il fera si la classe MyApiClient renvoie une réponse 500).

Je n'ai aucun problème à créer une version simulée de l'interface utilisateur. MyApiClient via l'outil standard de construction de maquettes de PHPUnit : Le problème que j'ai est de faire en sorte que mon contrôleur utilise cet objet pour plus d'une requête.

Je fais actuellement ce qui suit dans mon test :

class ApplicationControllerTest extends WebTestCase
{

    public function testSomething()
    {
        $client = static::createClient();

        $apiClient = $this->getMockMyApiClient();

        $client->getContainer()->set('myapiclient', $apiClient);

        $client->request('GET', '/my/url/here');

        // Some assertions: Mocked API client returns 500 as expected.

        $client->request('GET', '/my/url/here');

        // Some assertions: Mocked API client is not used: Actual MyApiClient instance is being used instead.
    }

    protected function getMockMyApiClient()
    {
        $client = $this->getMockBuilder('Namespace\Of\MyApiClient')
            ->setMethods(array('doSomething'))
            ->getMock();

        $client->expects($this->any())
            ->method('doSomething')
            ->will($this->returnValue(500));

        return $apiClient;
    }
}

Il semble que le conteneur soit en train d'être reconstruit lorsque la deuxième demande est faite, ce qui provoque l'apparition de l'erreur d'affichage. MyApiClient pour être à nouveau instancié. Le site MyApiClient est configurée pour être un service via une annotation (en utilisant le JMS DI Extra Bundle) et injectée dans une propriété du contrôleur via une annotation.

Si je le pouvais, je séparerais chaque requête dans son propre test pour contourner ce problème, mais malheureusement je ne le peux pas : je dois faire une requête au contrôleur via une action GET et ensuite POST le formulaire qu'il ramène. J'aimerais faire cela pour deux raisons :

1) Le formulaire utilise une protection CSRF, donc si j'envoie un POST directement au formulaire sans utiliser le crawler pour le soumettre, le formulaire échoue la vérification CSRF.

2) Tester que le formulaire génère la bonne requête POST lorsqu'il est soumis est un bonus.

Quelqu'un a-t-il des suggestions sur la manière de procéder ?

EDIT :

Ceci peut être exprimé dans le test unitaire suivant qui ne dépend d'aucun de mes codes, donc peut être plus clair :

public function testAMockServiceCanBeAccessedByMultipleRequests()
{
    $client = static::createClient();

    // Set the container to contain an instance of stdClass at key 'testing123'.
    $keyName = 'testing123';
    $client->getContainer()->set($keyName, new \stdClass());

    // Check our object is still set on the container.
    $this->assertEquals('stdClass', get_class($client->getContainer()->get($keyName))); // Passes.

    $client->request('GET', '/any/url/');

    $this->assertEquals('stdClass', get_class($client->getContainer()->get($keyName))); // Passes.

    $client->request('GET', '/any/url/');

    $this->assertEquals('stdClass', get_class($client->getContainer()->get($keyName))); // Fails.
}

Ce test échoue, même si j'appelle $client->getContainer()->set($keyName, new \stdClass()); immédiatement avant le deuxième appel à request()

0voto

Lebnik Points 48

Faites un simulacre :

$mock = $this->getMockBuilder($className)
             ->disableOriginalConstructor()
             ->getMock();

$mock->method($method)->willReturn($return);

Remplacer le service_name sur l'objet-mannequin :

$client = static::createClient()
$client->getContainer()->set('service_name', $mock);

Mon problème était d'utiliser :

self::$kernel->getContainer();

0voto

Roman Points 1

J'ai rencontré le même problème dans Symfony 4.4.

Après avoir lu Créer des mocks dans les tests fonctionnels d'api avec Symfony J'ai trouvé une solution - self::ensureKernelShutdown()

...    
$client->request('GET', '/any/url/');
$this->assertEquals('stdClass', get_class($client->getContainer()->get($keyName))); // Passes.

self::ensureKernelShutdown()

$client->request('GET', '/any/url/');
$this->assertEquals('stdClass', get_class($client->getContainer()->get($keyName))); // Passes.
...

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X