Kevin Pires
Kevin Pires

Reputation: 125

Mock call to external API

Today, I've tried to upgrade my project to the new version of Symfony (3.3), and I'm encountering a problem with my mocks.

Until today, I was doing my mocks like this:

$client     = $this->makeClient();
$mockObject = new \stdClass();

$mock = $this->getMockBuilder('SomeClass')
            ->disableOriginalConstructor()
            ->setMethods(['method1', 'method2'])
            ->getMock();

$mock->expects($this->once())
    ->method('method1')
    ->will($this->returnValue($mockObject));

$client->getContainer()->set('my_service', $mock);

Here, method1 is just a Guzzle post, nothing else.

Now, I'm getting the following error:

Setting the "my_service" pre-defined service is deprecated since Symfony 3.3 and won't be supported anymore in Symfony 4.0: 1x

After some research, it seems that I cannot use the last line of my code. Problem is, I can't see nor find any solution to solve this deprecation.

Upvotes: 1

Views: 2376

Answers (2)

Jakub Zalas
Jakub Zalas

Reputation: 36241

There's few ways of solving your problem.

TestDoubleBundle

TestDoubleBundle makes it easier to create test doubles. You can use dependency injection tags to automatically replace a service with either a stub or a fake.

Override the container

Another way is to extend the container in the test environment, so it allows stubbing. Here's a draft of the idea:

<?php

namespace Zalas\Test\DependencyInjection;

use Symfony\Component\DependencyInjection\Container;

class MockerContainer extends Container
{
    /**
     * @var object[] $stubs
     */
    private $stubs = array();

    public function stub($serviceId, $stub)
    {
        if (!$this->has($serviceId)) {
            throw new \InvalidArgumentException(sprintf('Cannot stub a non-existent service: "%s"', $serviceId));
        }

        $this->stubs[$serviceId] = $stub;
    }

    /**
     * @param string  $id
     * @param integer $invalidBehavior
     *
     * @return object
     */
    public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE)
    {
        if (array_key_exists($id, $this->stubs)) {
            return $this->stubs[$id];
        }

        return parent::get($id, $invalidBehavior);
    }

    /**
     * @param string $id
     *
     * @return boolean
     */
    public function has($id)
    {
        if (array_key_exists($id, $this->stubs)) {
            return true;
        }

        return parent::has($id);
    }
}

To enable this container you'll need to override the getContainerBaseClass method in your AppKernel:

/**
 * @return string
 */
protected function getContainerBaseClass()
{
    if ('test' == $this->environment) {
        return '\Zalas\Test\DependencyInjection\MockerContainer';
    }

    return parent::getContainerBaseClass();
}

You might need to tweak the code a bit, perhaps declare MockerContainer::$stubs as static (although if your previous approach worked it shouldn't be needed - it might be needed if you need to stub for multiple requests).

Now you should be able to use the container to stub services like this:

$client->getContainer()->stub('my_service', $myServiceStub);

Use a synthetic service

Another way of working around your issue is defining your service as synthetic. You could write a compiler pass that would only mark the service as synthetic in a test environment.

Upvotes: 2

Matteo
Matteo

Reputation: 39450

Your question is also discussed here.

You can take a look at this osservation:

Note that you don't need to fix the deprecations when moving to 3.3. But you will when moving to 4.0.

And this workaround:

Well, you can mark these tests as @legacy to avoid making them fail temporarily, to give you time to migrate the tests, if that takes time. This is the whole point of deprecations: you can migrate progressively (I also prefer removing deprecations as fast as possible, but for some of them, it may require more time)

Upvotes: 1

Related Questions