8000 Overriding services for functional testing · Issue #8203 · symfony/symfony-docs · GitHub
[go: up one dir, main page]

Skip to content

Overriding services for functional testing #8203

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
chalasr opened this issue Jul 21, 2017 · 10 comments
Closed

Overriding services for functional testing #8203

chalasr opened this issue Jul 21, 2017 · 10 comments

Comments

@chalasr
Copy link
Member
chalasr commented Jul 21, 2017

Next to symfony/symfony#23311

Some people are used to set() a service in the container for mocking it in functional tests (see https://stackoverflow.com/a/19726963/4363634) but as of 3.3, setting/resetting a predefined service is deprecated.

The right solution is to load testing specific config (symfony/symfony#21533 (comment)).
I think we should add an example to the docs.

@kastaneda
Copy link
Contributor

On Symfony master, CachePoolClearCommandTest I see this code

    protected function setUp()
    {
        static::bootKernel(array('test_case' => 'CachePoolClear', 'root_config' => 'config.yml'));
    }

And on KernelTestCase I see this

    protected static function bootKernel(array $options = array())
    {
        static::ensureKernelShutdown();
        static::$kernel = static::createKernel($options);
        static::$kernel->boot();
        return static::$kernel;
    }

    protected static function createKernel(array $options = array())
    {
        if (null === static::$class) {
            static::$class = static::getKernelClass();
        }
        return new static::$class(
            isset($options['environment']) ? $options['environment'] : 'test',
            isset($options['debug']) ? $options['debug'] : true
        );
    }

Maybe I completly miss the point, but what is the idea on «loading test specific config» here, if $options['root_config'] is never used?

@kastaneda
Copy link
Contributor

Upd.: sorry, I got that Symfony have different WebTestCase classes in diffenent namespaces.

@kastaneda
Copy link
Contributor

Hi @chalasr, I hope someone will write good example about doing that the Right Way.

I tried and now I give up.

public function setUp()
{
    static::bootKernel();
}

protected static function createKernel(array $opt = [])
{
    return new class('test', true) extends \AppKernel
    {
        public function registerContainerConfiguration(LoaderInterface $loader)
        {
            $loader->load(__DIR__ . '/my_config_with_mocked_service.yml');
        }
    };
}

This Schrödinger testcase works well, if it was executed right after cache:clear and before var/cache/test/appTestDebugProjectContainer.php poisoning. Otherwise it fails. Damnit.

@nicolas-grekas
Copy link
Member

Another option that could be worth documenting is described in symfony/symfony#23311 (comment):
we could tell ppl to turn these services as public and synthetic for the need.

@norbert-yoimo
Copy link

I would also be interested in a way to load a configuration on a per-test case basis. Making the services public/synthetic only for the sake of testing sounds wrong.

@nicolas-grekas
Copy link
Member

See symfony/symfony#24418

@afilina
Copy link
Contributor 8000
afilina commented Sep 9, 2020

None of these solutions truly replace the ability to ->set() a service on the fly. What if I want to stub methods with different values for each test case? I think that writing a whole bunch of fakes and configs is not a reasonable approach. I use the DIC for integration tests like this:

$clock = $this->createMock(Clock::class);
$clock->method('now')->willReturn(new DateTimeImmutable('2020-01-01'));
$this->container->set($clock);

$serviceUnderTest = $this->container->get(ServiceUnderTest::class);
$serviceUnderTest->doSomethingThatUsesClock();

As far as I know, every other DIC in PHP and other languages has this option.

@Wojciechem
Copy link
Contributor

@afilina you can create i.e. services_test.yaml and configure that service as public. Maybe not super nice, but does the trick.

services:
  my.foo.barizer:
    public: true
    synthetic: true

and then basing on KernelTestCase:

self::$container->set('my.foo.barizer', $this->createMock(FooBarizer::class));

@afilina
Copy link
Contributor
afilina commented Sep 25, 2020

@Wojciechem I tried that, but if it's not explicitly declared as synthetic, set would have no effect, not even an exception. This opens the door to accidentally write a test that gives a false positive. This can be especially bad in the context of a Clock, because tests can pass for a long time until they don't. It's quite an easy one to miss, especially for less experienced developers, whose work I can't always audit.

@Wojciechem
Copy link
Contributor

@afilina I might have made some wrong assumptions about your use case. Do you use KernelTestCase, or just build the container?

The service I mock is private, and I'm overriding it completely for test environment.
If the override is not marked as synthetic, exception is produced right away (something in lines of "you have to provide a class or mark service as synthetic explicitly").

However in specific case of Clock, I'd think of configuring FixedClock or TimeTravelClock for test container.
I'd argue that a lot can generally go wrong when less experienced developers are given the ability to mock freely.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants
0