Как смоделировать сервис с Symfony 4 в функциональных тестах?

У меня есть обработчик commandbus, который вводит некоторую службу:

class SomeHandler
{
    private $service;

    public function __construct(SomeService $service)
    {
        $this->service = $service;
    }

    public test(CommandTest $command)
    {
        $this->service->doSomeStuff();
    }
}

SomeService имеет метод doSomeStuff с внешними вызовами, который я не хочу использовать во время тестирования.

class SomeService
{
    private $someBindedVariable;

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

    public function doSomeStuff()
    {
        //TODO: some stuff
    }
}

В тесте я пытаюсь заменить сервис на фиктивный объект

public function testTest()
{
    $someService = $this->getMockBuilder(SomeService::class)->getMock();
    $this->getContainer()->set(SomeService::class, $someService);

    //TODO: functional test for the route, which uses SomeHandler
}

Первая проблема заключается в том, что этот код выдает исключение «Служба« App \ Service \ SomeService »является частной, вы не можете ее заменить».

Хорошо, попробуем сделать это общедоступным:

services.yaml:

App\Service\SomeService:
    public: true
    arguments:
        $someBindedVariable: 200

Но это не помогает. Я получаю ответ от собственного SomeService. Попробуем с псевдонимами:

some_service:
    class: App\Service\SomeService
    public: true
    arguments:
        $someBindedVariable: 200

App\Service\SomeService:
    alias: some_service

И снова фиктивный объект не используется тестом. Я вижу ответ от собственного SomeService.

Я пытался добавить опцию autowire, но это не помогло.

Что мне делать, чтобы заменить SomeService на какой-то фиктивный объект по всему проекту во время тестирования?

Все еще актуальная проблема, нашел ли кто-нибудь правильное решение для имитации службы в одном тесте?

M. Hardy 13.05.2020 12:37
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Symfony Station Communiqué - 17 февраля 2023 г
Symfony Station Communiqué - 17 февраля 2023 г
Это коммюнике первоначально появилось на Symfony Station , вашем источнике передовых новостей Symfony, PHP и кибербезопасности.
Управление ответами api для исключений на Symfony с помощью KernelEvents
Управление ответами api для исключений на Symfony с помощью KernelEvents
Много раз при создании api нам нужно возвращать клиентам разные ответы в зависимости от возникшего исключения.
9
1
5 165
3

Ответы 3

Лучший способ сделать это - явно определить эту службу и позволить ей быть параметрической, чтобы класс, в который вы вводите, мог быть основан на параметрах среды (так, в основном, определите другую реализацию для тестирования и разработки службы, которую вы пытаюсь ввести).

В принципе, что-то вроде

<service id = "yourService">
  <argument type = "service" id = "%parametric.fully.qualified.class.name%" />
</service>

Затем вы можете определить в common.yaml FQCN для реальной реализации.

parameters:
    parametric.fully.qualified.class.name: Fully\Qualified\Class\Name\Real\Impl

и в test.yaml (который должен быть загружен послеcommon.yaml, чтобы переопределить его) реализация заглушки

parameters:
    parametric.fully.qualified.class.name: Fully\Qualified\Class\Name\Stub\Impl

И все готово, не касаясь ни строчки кода, ни строчки тестового файла: просто конфигурация.

Обращать внимание

Это только концептуальный и иллюстративный пример, вы должны адаптировать его к своему коду (см., Например, common.yaml и test.yaml) и к именам ваших классов (см. yourService и все вещи FQCN)

Более того, в symfony 4 файлы конфигурации могут быть заменены файлом .env для получения того же результата, вам просто нужно адаптировать эти концепции.

Просто хотел указать, что конфигурационные файлы на основе среды живы в S4. Я также немного сомневаюсь, что ваш подход позволит использовать фиктивный объект. С другой стороны, я немного сомневаюсь в подходе, который пытается применить опера.

Cerad 29.05.2018 14:04

@Cerad Я не был уверен в этих файлах конфигурации. Я сделал правку. Мой подход работает до тех пор, пока фиктивные объекты являются реальными объектами, которые что-то возвращают, поэтому не макетами в собственном смысле, а скорее как заглушки. Я много использовал их в своих тестах ingegration / e2e и должен администрировать, что с почти нулевым кодом я могу получить то, что просит OP. Почему вы сомневаетесь в запросе OP?

DonCallisto 29.05.2018 14:07

Как я прочитал вопрос, они хотят использовать настоящий издевательский объект. И они хотят, чтобы этот объект заменил существующий объект в контейнере. Я могу ошибаться.

Cerad 29.05.2018 14:21

@Cerad, ты прав, если им нужен макет настоящий, а не заглушка. Знаете, заглушка / макет часто (ошибочно) используются как синонимы. Я понимаю, что им нужно что-то «подделать», чтобы не вызывать фактическую реализацию, поэтому внимание не уделяется какие, а только тому, что будет возвращено. Не знаю, прав ли я, но в этом я почти уверен: для изменения служб в таких сценариях лучше предоставить что-то настраиваемое. Если им нужен макет, я предлагаю предложить установку установщика, но не знаю, как это работает с autowire (на самом деле я не вижу никакой проблемы)

DonCallisto 29.05.2018 14:31

У меня такая же проблема с издевательством. Но в моем случае мне нужно имитировать ApiClient, чтобы избежать реальных вызовов API в тестовой среде, и мне нужно сделать этот макет настраиваемым, чтобы иметь возможность имитировать любой запрос / ответ для каждого тестового примера в реальном времени или добавить несколько пар запрос / ответ, поскольку в некоторых случаях требуется несколько вызовов API к разным конечным точкам для одного функционального тестового примера. То, как это было раньше, было очень легко реализовать и прочитать:

 $apiClientMock = \Mockery::mock(HttpClientInterface::class);
 $apiClientMock
            ->shouldReceive('send')
            ->with($request)/            
            ->andReturn(new Response(HttpCode::OK, [], '{"data":"some data"}'))
            ->once();

 $I->replaceSymfonyServiceWithMock($apiClientMock, HttpClientInterface::class);

поэтому в приведенном выше коде я могу добавить столько реализаций, сколько хочу для каждого тестового примера. Но сейчас в Symfony не работает. Я не знаю, почему они не могут сделать контейнер для тестового env, как они сделали здесь https://symfony.com/blog/new-in-symfony-4-1-simpler-service-testing, но также с возможностью устанавливать службы в реальном времени (я считаю, что есть причина, по которой они не могут, я не очень знаком с это).

Что я могу, так это создать другую реализацию службы и добавить ее в services_test.yml (этот файл конфигурации читается во время тестов, поэтому вам не нужно передавать имя класса в качестве параметра env) и использовать его в тестовой среде, но я все еще не могу добавить к нему ожидания для каждого тестового примера, то, что я могу, - это просто жестко закодировать все ожидания в этой реализации, но это грязное решение, как для меня, и простая задача, как создание макета, становится настоящей головной болью, поскольку вам нужно создать много код просто для замены некоторых функций класса, и это усложняет процесс создания тестов, но я думаю, что это должно быть просто

Для имитации и тестирования сервисов вы можете поставить такую ​​конфигурацию:

конфигурация / пакеты / тест / service.yaml

'test.myservice_mock':
    class: App\Service\MyService
    decorates: App\Service\MyService
    factory: ['\Codeception\Stub', makeEmpty]
    arguments:
      - '@test.myservice_mock.inner'

Другие вопросы по теме