У меня есть обработчик 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 на какой-то фиктивный объект по всему проекту во время тестирования?




Лучший способ сделать это - явно определить эту службу и позволить ей быть параметрической, чтобы класс, в который вы вводите, мог быть основан на параметрах среды (так, в основном, определите другую реализацию для тестирования и разработки службы, которую вы пытаюсь ввести).
В принципе, что-то вроде
<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 Я не был уверен в этих файлах конфигурации. Я сделал правку. Мой подход работает до тех пор, пока фиктивные объекты являются реальными объектами, которые что-то возвращают, поэтому не макетами в собственном смысле, а скорее как заглушки. Я много использовал их в своих тестах ingegration / e2e и должен администрировать, что с почти нулевым кодом я могу получить то, что просит OP. Почему вы сомневаетесь в запросе OP?
Как я прочитал вопрос, они хотят использовать настоящий издевательский объект. И они хотят, чтобы этот объект заменил существующий объект в контейнере. Я могу ошибаться.
@Cerad, ты прав, если им нужен макет настоящий, а не заглушка. Знаете, заглушка / макет часто (ошибочно) используются как синонимы. Я понимаю, что им нужно что-то «подделать», чтобы не вызывать фактическую реализацию, поэтому внимание не уделяется какие, а только тому, что будет возвращено. Не знаю, прав ли я, но в этом я почти уверен: для изменения служб в таких сценариях лучше предоставить что-то настраиваемое. Если им нужен макет, я предлагаю предложить установку установщика, но не знаю, как это работает с autowire (на самом деле я не вижу никакой проблемы)
У меня такая же проблема с издевательством. Но в моем случае мне нужно имитировать 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'
Все еще актуальная проблема, нашел ли кто-нибудь правильное решение для имитации службы в одном тесте?