-
-
Notifications
You must be signed in to change notification settings - Fork 107
Description
This is not really a bug report or a feature request. But since it took me some time to investigate and understand what's going on, I thought I might drop the information here, hoping that it might be useful for others, a future me – or maybe it brings insights and helps to draw conclusions or see parallels with other issues. Let's go.
I am using Behat for acceptance testing and Foundry to create objects (mostly Doctrine Entities) from my Behat Context classes. Basically, these context classes contain the fragments of code that are wired together by the tests specification written in Gherkin.
To integrate with Symfony, I use the https://github.com/FriendsOfBehat/SymfonyExtension/ Behat extension. This extension does two things:
- When Behat starts, it creates and boots an instance of the Symfony kernel and prepares the DIC. This allows to have Behat Context classes that can be created from or wired with services by the Symfony container.
- When making requests to the Symfony application in functional (or "end-to-end") testing, this extension takes care of creating a second
Kernelinstance, booting and resetting it between each scenario. This second Kernel is what is running the application (the system under test).
This is also described in https://github.com/FriendsOfBehat/SymfonyExtension/blob/master/DOCUMENTATION.md#isolated-kernel-and-container-for-the-mink-driver. That way, you can make sure that the application's kernel is clean in a way that it has not been affected by the test setup phase. In particular, I care about the entity manager being in a clean state, not holding anything in its Identity Map and loading everything from the database.
The problem challenge is that ZenstruckFoundryBundle::boot() is called multiple times – the first time when the "outer" Kernel and container (the one Behat is running on/within) is created and booted. And then once before each scenario when the SymfonyExtension resets/reboots the "inner" (application) Kernel.
This leads to Configuration::boot() being called with the .zenstruck_foundry.configuration from the "outer" container the very first time, and then with new instances of that before every scenario starts. This means that Foundry in most cases is actually using the application's entity manager instance to persist entities.
I have created the following Behat context class to work around that:
<?php
namespace App\Behat;
use Behat\Behat\Context\Context;
use Behat\Hook\BeforeScenario;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Zenstruck\Foundry\Configuration;
class FoundryContext implements Context
{
public function __construct(
#[Autowire(service: '.zenstruck_foundry.configuration')]
private readonly Configuration $configuration
) {
}
#[BeforeScenario]
public function setUpFactories(): void
{
Configuration::boot($this->configuration);
}
}Since it's a Behat context, it is wired with the service from the "outer" container. And it happens that the setUpFactories() BeforeScenario hook is called late enough, after the respective boot() of the inner container for that particular scenario... altough I am not sure if that's just a coincidence or a guarantee.
I understand that the singleton-style pattern applied to Configuration is necessary to make the .zenstruck_foundry.configuration available to factories that have no means to otherwise access it. On the other hand, global state like that is prone to get messed up or leak between tests.
Maybe you have already thought about a way to give users a tad more control about how or when the Configuration is booted, without necessarily combining it with the bundle's boot() method?