Tagging Scenarios in order to Load Fixtures

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

Remember way back in the beginning when we had search feature? Try running that again:

./vendor/bin/behat features/web/search.feature

Huh, this one is failing now: it says that the text "Samsung Galaxy" was not found anywhere on the page. Now that you're an expert, I hope you can spot the problem: we're not adding this product at the beginning of the scenario. This worked originally because the fixtures that come with the project have a "Samsung Galaxy" product. But now that other tests have cleared the database, we're in trouble.

We could put some Given statements at the top to add the products. But there's another way: load the project's fixtures automatically before the scenario. This LoadFixtures class is responsible for putting in the Kindle and Samsung products.

I think that entering the data manually with the Given statements is the most readable way to do things. But, if you do load the fixtures, here's the best way. First, I don't want to load fixtures before every scenario. That would make my scenarios run slower, even when I don't need that stuff.

Tagging Scenarios

Instead, I need a way to tag scenarios to say "this one needs fixtures". Add @fixtures at the top of this scenario outline:

Feature: Search
In order to find products dinosaurs love
As a website user
I need to be able to search for products
... lines 6 - 8
@fixtures
Scenario Outline: Search for a product
... lines 11 - 19

That's called a tag, and you can put many as you want, separating each by a space. At first, adding a tag does nothing except for the magic @javascript that changes to use a JavaScript driver.

Running things Before a Tagged Scenario

But in FeatureContext you can add an @BeforeScenario method that's only executed when a scenario has a certain tag. Make a new public function loadFixtures(). Inside, just to see if it's working, put var_dump('GO!');. Above, put the normal @BeforeScenario:

... lines 1 - 42
/**
* @BeforeScenario @fixtures
*/
public function loadFixtures()
{
var_dump('GO!');
}
... lines 50 - 258

Here's the trick: after this, add @fixtures. Now, this will only run for scenarios tagged with @fixtures. To prove that, re-run our search.feature:

./vendor/bin/behat features/web/search.feature

There's our 'GO!'. Now run the authentication.feature:

./vendor/bin/behat features/web/authentication.feature

This passes with no var_dump(). Perfect!

Loading the Fixtures

One way to execute the fixture is by running the doctrine:fixtures:load command. I use a different method that gives me more control. Add $loader = new ContainerAwareLoader() and pass it the container:

... lines 1 - 47
public function loadFixtures()
{
$loader = new ContainerAwareLoader($this->getContainer());
... lines 51 - 53
}
... lines 55 - 263

Now, point to the exact fixtures objects that you want to load. There are two methods available: loadFromDirectory() or loadFromFile(). Move up a few directories and load from src/AppBundle/DataFixtures:

... lines 1 - 50
$loader->loadFromDirectory(__DIR__.'/../../src/AppBundle/DataFixtures');
... lines 52 - 263

That should do it!

Next, create an $executor = new ORMExecutor() and pass it the entity manager:

... lines 1 - 51
$executor = new ORMExecutor($this->getEntityManager());
... lines 53 - 263

A purger is the second argument, which you only need if you want to clear out data. We're already doing that, so I'm not going to worry about it here. Finally type $executor->execute($loader->getFixtures()) and pass true as the second argument:

... lines 1 - 52
$executor->execute($loader->getFixtures(), true);
... lines 54 - 263

This says to not delete the data, but to append it instead.

Ok, run search.feature:

./vendor/bin/behat features/web/search.feature

It fails for a completely different reason. Things are never boring here! This is a unique constraint violation because it's not clearing out the data before loading the fixtures. This is a funny edge case. Because the new @BeforeScenario is near the top and the other for clearing the data is lower, they're being run in that order. Move these @BeforeScenarios up top and keep them in the order that you want:

... lines 1 - 35
/**
* @BeforeScenario
*/
public function clearData()
{
$purger = new ORMPurger($this->getContainer()->get('doctrine')->getManager());
$purger->purge();
}
/**
* @BeforeScenario @fixtures
*/
public function loadFixtures()
... lines 49 - 263

Back to the terminal and run this sucker again!

./vendor/bin/behat features/web/search.feature

Pop the champagne people, it passes! It clears the data and then loads the fixtures. And life is super awesome!

Running Tagged Scenarios

There's another benefit to tagging scenarios. But first, if you ever need some details about the behat executable, run it with a --help flag to get all the info:

./vendor/bin/behat --help

One of the options is tags:

Only execute the features or scenarios with these tags

Well that's sweet. So we could say: --tags=fixtures and it will only execute scenarios tagged with fixtures:

./vendor/bin/behat --tags=fixtures

Or, we can get real crazy and say that we want to run all scenarios except the ones tagged with @fixtures by using the handy ~ (tilde) character:

./vendor/bin/behat --tags=~fixtures

behat -vvv

One more tip! If something goes wrong, there's also a verbosity option that will show you the full stack trace. Just add -v:

./vendor/bin/behat --tags=~fixtures -v

Hey, that's all! Hop in there, celebrate behavior driven development, create beautiful tests and sleep better at night!

See ya next time!

Leave a comment!

This tutorial uses a very old version of Symfony. The fundamentals of Behat are still valid, but integration with Symfony will be different.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.4.0, <7.3.0",
        "symfony/symfony": "^2.7", // v2.7.4
        "twig/twig": "^1.22", // v1.22.1
        "sensio/framework-extra-bundle": "^3.0", // v3.0.16
        "doctrine/doctrine-bundle": "^1.5", // v1.5.1
        "doctrine/orm": "^2.5", // v2.5.1
        "doctrine/doctrine-fixtures-bundle": "^2.2", // v2.2.1
        "behat/symfony2-extension": "^2.0" // v2.0.0
    },
    "require-dev": {
        "behat/mink-extension": "^2.0", // v2.0.1
        "behat/mink-goutte-driver": "^1.1", // v1.1.0
        "behat/mink-selenium2-driver": "^1.2", // v1.2.0
        "phpunit/phpunit": "^4.8" // 4.8.18
    }
}