Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock service with symfony 4 in functional tests?

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?

like image 685
abr_stackoverflow Avatar asked May 28 '18 21:05

abr_stackoverflow


2 Answers

I have a pretty same issue with mocking. But in my case, I need to mock ApiClientto 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

like image 147
Bogdan Dubyk Avatar answered Sep 24 '22 13:09

Bogdan Dubyk


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'
like image 20
Alessandro Giuliani Avatar answered Sep 26 '22 13:09

Alessandro Giuliani