I have an commandbus handler, which injects some service:
class SomeHandler
{
private $service;
public function __construct(SomeService $service)
{
$this->service = $service;
}
public test(CommandTest $command)
{
$this->service->doSomeStuff();
}
}
SomeService has method doSomeStuff with external calls, which I want not to use during testing.
class SomeService
{
private $someBindedVariable;
public function __construct($someBindedVariable)
{
$this->someBindedVariable = $someBindedVariable;
}
public function doSomeStuff()
{
//TODO: some stuff
}
}
There is in the test I try to replace service with mock object
public function testTest()
{
$someService = $this->getMockBuilder(SomeService::class)->getMock();
$this->getContainer()->set(SomeService::class, $someService);
//TODO: functional test for the route, which uses SomeHandler
}
The first problem is this code will throws exception "The "App\Service\SomeService" service is private, you cannot replace it."
Ok, let's try to make it public:
services.yaml:
App\Service\SomeService:
public: true
arguments:
$someBindedVariable: 200
But it doesn't help. I get response from native SomeService. Let's try with aliases:
some_service:
class: App\Service\SomeService
public: true
arguments:
$someBindedVariable: 200
App\Service\SomeService:
alias: some_service
And again the mock object does not use by test. I see response from native SomeService.
I tried to append autowire option, but it did not help.
What should I do to replace my SomeService with some mock object all over the project during test?
I have a pretty same issue with mocking. But in my case, I need to mock ApiClient
to avoid making real API calls in the test env and I need to make this mock configurable, to have ability mock any request/response on per test case basis in the real-time or to add a few pairs of request/response as some cases requiring multiple API calls to different endpoints for single functional test case. The way it was before was very easy to implement and read:
$apiClientMock = \Mockery::mock(HttpClientInterface::class);
$apiClientMock
->shouldReceive('send')
->with($request)/
->andReturn(new Response(HttpCode::OK, [], '{"data":"some data"}'))
->once();
$I->replaceSymfonyServiceWithMock($apiClientMock, HttpClientInterface::class);
so in a code above, I can add as many realizations I want per test case. But now in Symfony is not working. I do not know why they can't make a container for test env as they made here https://symfony.com/blog/new-in-symfony-4-1-simpler-service-testing but also with the ability to set services in real time (I believe there is a reason they can't, I'm not very familiar with this).
What I can is to create different service realization and add it to services_test.yml (this config file is reading while tests so you do not need to pass class name as an env parameter), and use it in the testing env, but I still can't add expectation to it on per test case basis, what I can is just hardcode all expectation in that implementation but its dirty solution as for me, and simple task as creating mock become a real headache as you need to create a lot of code just to replace some class functionality and it makes process of creating tests complicated, but I think it should be straightforward
To mock and test services you can put this configuration :
config/packages/test/service.yaml
'test.myservice_mock':
class: App\Service\MyService
decorates: App\Service\MyService
factory: ['\Codeception\Stub', makeEmpty]
arguments:
- '@test.myservice_mock.inner'
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With