How to rollback commits in Behat 3 functional tests with Symfony2 and Doctrine?
If your context implements the KernelAwareContext
, then in the @BeforeScenario and @AfterScenario annotated methods, you can do
$this->kernel->getContainer()->getDoctrine()->getConnection()->beginTransaction();$this->kernel->getContainer()->getDoctrine()->getConnection()->rollBack();
This is assuming you only have one connection and it's used by the em.
You can also try $connection->setRollbackOnly()
but bear in mind that it will wildly depend on your underlying db. Mysql might autocommit in quite a few case were your didn't expected it.
And lastly there is also $connection->createSavepoint('savePointName')
to use with $connection->rollbackSavepoint('savePointName')
This is out of my head so it might needs some adjustments.
The problem is that Behat uses the client from the Browser-Kit, so you are suffering from rebootable client.Luckily the Symfony2 extension fetches the client from the container, so we can override it. Here is what did the trick for me:
Create a wrapper class:
class NoneRebootableClient extends Client{ public function __construct(KernelInterface $kernel, array $server = array(), History $history = null, CookieJar $cookieJar = null) { parent::__construct($kernel, $server, $history, $cookieJar); $this->disableReboot(); } public function setServerParameters(array $parameters) { return; }}
The setServerParameters
override is only required if you have something like "Before each scenario, set an auth header". As Behat resets the headers after each call in vendor/behatch/contexts/src/HttpCall/Request/BrowserKit.php:resetHttpHeaders
we would loose the auth header after the first call. Note: this might have unexpected side-effects.
Then configure the service for the test-env (the test.client
id is important, its the id that behat uses for the client lookup):
test.client: class: App\Tests\NoneRebootableClient public: true arguments: - '@kernel' - [] - '@test.client.history' - '@test.client.cookiejar'
And then use the Before/After Scenario in your context as you already described above:
/** * @BeforeScenario */public function startTransaction($event){ $this->doctrine->getConnection()->beginTransaction();}/** * @AfterScenario */public function rollbackTransaction($event){ $this->doctrine->getConnection()->rollBack();}
Et voilĂ , we are idempotent :)
With setUp
method, you can begin a transaction. You can rollback it in tearDown
method IF there is no commit between the two method calls.
It's very dangerous to launch tests on a production database even if you rollback queries. It's a better way to initialize a database test with data fixture. If you can not do it (I think so), you want to test with production data. Use doctrine:migrations
(or doctrine:schema:create
) to copy your production database schema into your dev environnement and add a script to copy data.