Skip to content
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

Replace DIC services with mocks in tests #29109

Closed
Bilge opened this issue Nov 6, 2018 · 11 comments
Closed

Replace DIC services with mocks in tests #29109

Bilge opened this issue Nov 6, 2018 · 11 comments

Comments

@Bilge
Copy link
Contributor

Bilge commented Nov 6, 2018

Description
Since 4.1 we can access private services in tests but we cannot substitute any services with mocks, as may be required by some testing strategies.

Example

self::$container->set('foo', new MockedFoo());

Symfony\Component\DependencyInjection\Exception\InvalidArgumentException : The "foo" service is private, you cannot replace it.
vendor\symfony\dependency-injection\Container.php:163
vendor\symfony\framework-bundle\Test\TestContainer.php:86

@unkind
Copy link
Contributor

unkind commented Nov 6, 2018

You can achieve the same with proper configuration by using suffixes with env names.

@Bilge
Copy link
Contributor Author

Bilge commented Nov 6, 2018

Not sure what you mean by that exactly, but if you're referring to env configs (e.g. services_test.yaml), that implies the same mock must be used for all tests, which is not good enough. Each test should be able to discretely configure a mock to suit its requirements.

@nicolas-grekas
Copy link
Member

Each test should be able to discretely configure a mock to suit its requirements.

That requirement is actually incompatible with having an optimized container; that's why it doesn't work. Private services can be inlined, and any reference to them are optimized out. As the message suggests, you should make the service public if you really want to do this.

Closing as there is no bug here. Thanks for reporting.

@Bilge
Copy link
Contributor Author

Bilge commented Nov 6, 2018

But I only want it to be public, or more accurately: replaceable, in tests.

@nicolas-grekas
Copy link
Member

Can you try creating a public alias, then setting the service using that alias in your test?

@TiMESPLiNTER
Copy link

@nicolas-grekas solution works like a charm. ❤️

@bcastagna
Copy link

But it's really cumbersome to make an alias for every service we want to mock..... Isn't there a way to make all services in container public for testing purposes ?
For example I want to mock translator.default service to verify if the call to the service is effectively made with the expected string in my service's unit test.

@Rubinum
Copy link
Contributor

Rubinum commented Dec 7, 2020

@bcastagna You can change the service.yaml in your project to replace your services with a mocked one from your tests or if you want them to be public, you can set it there too.
When you excecute tests, you can define a service_test.yaml that only applies on your testing environment. You have to make sure your application has been set to this environment during tests, if you diverge from the standard. In that case you can make every service public or replace it with a better one for your tests.

Example: I have a controller action where the Finder Component of Symfony searches in a specific directory on the mashine for some config files. In tests, these directories may not exists because my application is bound to another app on the same mashine. To build better and more independent tests, I want to mock that Finder and replace it with a Finder, that has been modified so that the directory is one in the tests folders.

service_test.yaml

services:
    Symfony\Component\Finder\Finder:
        public: true
        shared: false

MyTest.php

class MyTest extends WebTestCase {
    public function testMyControllerAction(): void
    {
        $this->client->getContainer()->set('Symfony\Component\Finder\Finder', new ModifiedTestFinder());
         // do some assertions
    }
}

ModifiedTestFinder.php

use Symfony\Component\Finder\Finder;

class ModifiedTestFinder extends Finder
{
    public function getIterator()
    {
        return new \ArrayIterator([new \SplFileInfo(__DIR__.'/../tenantTemplateTestDirectory')]);
    }
}

Alternatively, you can overwrite the Symfony Finder for all tests.

service_test.yaml

services:
    Symfony\Component\Finder\Finder:
        class: 'App\Tests\ModifiedTestFinder'

But be aware, that modifying the container during tests can lead to unexpected behavior during excecution of your tests. This can lead to a diverged behavior between prod and test environment, if you are not careful. It depends on how good you manage your tests.

@BafS
Copy link
Contributor

BafS commented Jan 18, 2023

Good new, it is happening: #48938, we can probably close this 4+ years old issue.

@Bilge
Copy link
Contributor Author

Bilge commented Jan 18, 2023

...but it is closed... and has been for almost 5 years...

Nevertheless, thanks for bringing it to my attention 👍🏻

@BafS
Copy link
Contributor

BafS commented Jan 18, 2023

Oh right, the purple color made me think it was still open, sorry

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

No branches or pull requests

7 participants