Next to https://github.com/symfony/symfony/issues/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 (https://github.com/symfony/symfony/pull/21533#issuecomment-302101055).
I think we should add an example to the docs.
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?
Upd.: sorry, I got that Symfony have _different_ WebTestCase classes in diffenent namespaces.
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.
Another option that could be worth documenting is described in https://github.com/symfony/symfony/issues/23311#issuecomment-329154632:
we could tell ppl to turn these services as public and synthetic for the need.
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.
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.
@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));
@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.
@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.
Most helpful comment
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.