首页 > 解决方案 > Why does Symfony DI container multiplies instances of a service when API tests are run?

问题描述

I have a service that should have only one instance and I was sure that DI container takes care about it. And when I send a request by Postman everything works properly, container returns always the very same instance. I have even added an additional field with a random number generated during object creation for debugging.

class ReadModel
{
    private ?Database $database;

    // Temporary field for debugging purpose
    private int $tmp;

    public function __construct()
    {
        $this->database = null;

        $this->tmp = rand(1, 1000);
    }

    public function setDatabase(Database $database): void
    {
        $this->database = $database;

        dump($this->tmp); // returns e.g. 836 when codeception test is run
    }

    public function getCollection(string $collectionName): Collection
    {
        dump($this->tmp); // returns e.g. 390 when codeception test is run

        return $this->database->selectCollection($collectionName);
    }
}

When a request is sent by Postman (dev environment) both numbers returned by dump() function are equal (the same instance is injected to other services that uses it). But when I run a Codeception test, this service behaves differently. When I call setDatabase() method of ReadModel in one service and getCollection methond in another one, I receive two different values of $tmp field. That means that two different instances of ReadModel service have been injected to my services. How is it possible and what can I do to have the same behavior in both environments (dev and test)?

ReadModel service is autowired so I don't paste services.yaml content.

My stack: Symfony 5.1, Codeception 4.1

PS. By Codeception tests I mean end2end API tests like this one, not unit tests:

<?php

use Codeception\Util\HttpCode;

class CreateProductCest
{
    public function _before(ApiTester $I)
    {
        // Authentication stuff...
    }

    public function shouldCreateProductWithNoException(ApiTester $I)
    {
        $data = [
            'name' => 'test123',
        ];

        $I->sendPost('/product/create', $data);

        $I->seeResponseIsJson();
        $I->seeResponseCodeIs(HttpCode::OK);
        $I->seeResponseContainsJson([]);
    }
}

Examples of method calls of ReadModel object. Most of the code is cut off, I leaved only relevant parts of code.

class JwtDecodedListener
{
    private ReadModel $readModel;

    public function __construct(
        ReadModel $readModel
    ) {
        $this->readModel = $readModel;
    }

    public function onJWTDecoded(JWTDecodedEvent $event)
    {
        // some code
        $client = new Client("mongodb://{$mongoDbUser}:{$mongoDbPass}@{$this->mongoHost}/{$mongoDb}");
        $this->readModel->setDatabase($client->selectDatabase($mongoDbUser));
    }
}
class ProductProjection
{
    private ReadModel $readModel;

    public function __construct(
        ReadModel $readModel
    ) {
        $this->readModel = $readModel;
    }

    public function whenProductWasCreated(Product $product): void
    {
        // $collectionName is from a factory
        $collection = $this->readModel->getCollection($collectionName);
    }
}

Edit: As suggested in the comments I added dump() call in the constructor of ReadModel class and my conclusion is the same. Post request executed from postman - one constructor cal. The same request executed by Codeception - 2 constructor calls. Still have no idea what's the reason.

标签: phpsymfonydependency-injectionphpunitcodeception

解决方案


推荐阅读