Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to rollback commits in Behat 3 functional tests with Symfony2 and Doctrine?

As the title, my goal is to rollback any commit made during Behat functional tests. I checked this answer very similar, but it is from two years ago and it seems impossible to do.

Maybe with Behat 3 now it's possible.

I know that with PHPUnit I can reach something like that using startUp and tearDown methods.

I tried to start and rollback a transaction hooking with the @BeforeScenario and @AfterScenario annotations, but it seems that behat and the application doesn't share the same instance of entity manager.

Some advice on it?

Thank you.


UPDATE

Thank you all for your advices. Here some new considerations:

  • LOAD FIXTURES: Yes, it works. I'm able to run fixtures before my tests starts, but the problem (my fault to not mention it) is that fixtures sometimes needs several minutes to load and it is annoying to wait 10 or more minutes before your tests starts.

  • BEGIN/ROLLBACK TRANSACTION: It works too or it seems to be. I receive no errors, but the data written during tests is still in my database when they ended. I added the first in a method tagged @BeforeScenario e the latter in a method tagged with @AfterScenario

$this->kernel->getContainer()
    ->get('doctrine.orm.entity_manager')
    ->getConnection()
    ->beginTransaction();

$this->kernel->getContainer()
   ->get('doctrine.orm.entity_manager')
   ->getConnection()
   ->rollBack();
  • SAVEPOINT: I think that's exactly what I need, but my data is still there. I tried to add the creation of the savepoint in my @BeforeScenario method and the rollback on my @AfterScenario method
public function gatherContexts(BeforeScenarioScope $scope) {
    $environment = $scope->getEnvironment();
    $connection = $this->kernel->getContainer()->get('doctrine.orm.entity_manager')->getConnection();
    $connection->beginTransaction();
    $connection->createSavepoint('tests');
}

public function rollback(AfterScenarioScope $scope) {
    $connection = $this->kernel->getContainer()->get('doctrine.orm.entity_manager')->getConnection();
    $connection->rollbackSavepoint('tests');
}

All these tests are used to test my API REST project. After these considerations I think that Behat and my application doesn't share the same instance of the entity manager. Are you able to share the same exactly instance between your tests and your projects during tests?

like image 397
stuzzo Avatar asked Oct 28 '15 23:10

stuzzo


2 Answers

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.

like image 121
Mike Simonson Avatar answered Oct 19 '22 02:10

Mike Simonson


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 :)

like image 1
leberknecht Avatar answered Oct 19 '22 02:10

leberknecht