feature #65997 [FrameworkBundle] Add a "configure_container" option to WebTestCase::createClient() (nicolas-grekas)
This PR was merged into the 8.2 branch.
Discussion
----------
[FrameworkBundle] Add a "configure_container" option to WebTestCase::createClient()
| Q | A
| ------------- | ---
| Branch? | 8.2
| Bug fix? | no
| New feature? | yes
| Deprecations? | no
| Issues | Fix #49930
| License | MIT
| Doc PR | symfony/symfony-docs#22979
`KernelBrowser::doRequest()` reboots the kernel on every request after the first one. That includes crawler-driven requests: `click()`, `submit()` and redirect following all go through the same code path. So a service replaced with `self::getContainer()->set(...)` survives exactly one request and then silently vanishes: the test gets the real service back, with no error and no warning.
This adds a `configure_container` option to `WebTestCase::createClient()`, holding a closure that is applied to the container after *every* kernel boot, for the lifetime of the client:
```php
$client = self::createClient(['configure_container' => function (ContainerInterface $container) {
$container->set(ClockInterface::class, $clock);
}]);
$crawler = $client->request('GET', '/');
$clock->sleep(3600);
$client->click($crawler->selectLink('Next')->link()); // reboots, and the closure is applied again
```
The closure receives the container returned by `KernelBrowser::getContainer()`, which resolves `test.service_container` when available, so private services can be replaced. It runs on a freshly booted, untouched container, which is required since `TestContainer::set()` refuses to replace a private service that has already been initialized.
Why `$options` and not a new parameter on `createClient()`: overriding `createClient()` in a project base test case is a very common pattern, and PHP fatals when a child method declares fewer parameters than its parent. Adding a third argument would break every such override. `KernelTestCase::createKernel()` already documents `$options` as an extensible bag and reads only the keys it knows about, so this fits. `createClient()` extracts the key and `unset()`s it before booting, so a user-defined `createKernel()` never sees the closure.
Notes:
* with `disableReboot()` there is no new boot, so the closure is not re-applied (the container simply keeps what was set);
* insulated requests run in a separate process and serialize their state, which a closure cannot cross, so registering a configurator and calling `insulate()` now throws a clear `LogicException` instead of failing obscurely;
* passing a non-closure under the key throws an `InvalidArgumentException`;
* `KernelBrowser::setContainerConfigurator()` is public but ``@internal`` for now, to keep the public surface minimal. It can be opened up later if re-registering mid-test turns out to be useful.
The documentation is symfony/symfony-docs#22979. Note that `testing.rst` currently recommends `$container->set(...)` under "Mocking Dependencies" with no mention of the reboot, so that PR also adds a warning there.
Thanks to `@lyrixx` for reporting the issue, and to `@nikophil` and `@benito103e` who both volunteered to implement it and never got an answer.
Commits
-------
1e85f7e708d [FrameworkBundle] Add a "configure_container" option to WebTestCase::createClient() N
Nicolas Grekas committed
8b662f974d5e1c738ee6128cc14faabec720c73a