Buy

Tagging Scenarios in order to Load Fixtures

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:

19 lines features/web/search.feature
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:

258 lines features/bootstrap/FeatureContext.php
... 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:

263 lines features/bootstrap/FeatureContext.php
... 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:

263 lines features/bootstrap/FeatureContext.php
... 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:

263 lines features/bootstrap/FeatureContext.php
... 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:

263 lines features/bootstrap/FeatureContext.php
... 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:

263 lines features/bootstrap/FeatureContext.php
... 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!

  • 2015-12-16 Josh Crawmer

    This worked great. Thanks a lot. This is kind of a weird piece of code:

    $executer = new ORMExecutor($this->getEntityManager());
    $executer->execute($loader->getFixtures(), true);

    If is supply a new ORMPurger($em) object as the second argument to ORMExecuter(), then suddenly $executer->execute won't run unless I change true to false as the second argument. I just find it a little weird that what you pass to the second method is dependent upon what you pass to the constructor. I dunno. Maybe I'm being weird. Thanks for everything!

  • 2015-12-16 weaverryan

    Hey Josh!

    Yep, now I see the problem: move clearData() *above* loadFixtures() - we talk about this near the bottom of this section: https://knpuniversity.com/scre.... For you, it's adding the fixtures, and then clearing everything. Hence, no results :). It's a tricky one.

    Cheers!

  • 2015-12-16 Josh Crawmer

    Hey Ryan. The var_dump inside the loadFixtures works fine. I also added a "And print last response" and on the page it showed a "No products found message" and the table was empty. It looks like my Features are running fine. It just seems that the fixtures are not getting loaded, once the loadFixtures method is called. Here is my code if you can look over quick and see if I missed something. Thanks! https://github.com/joshcrawmer...

  • 2015-12-15 weaverryan

    Yo Josh!

    If everything worked up to this point, it must be something small! If you add a var_dump('hey!') to loadFixtures and run just this one Scenario Outline, is it called? Also, if you add a "And print last response" to the outline, what do you see? Does the page work? Are the products visible on the page?

    It could be several things - use the debugging tools from earlier: I'm sure we'll find the issue. Nothing stands out to me - so you must be close.

    Cheers!

  • 2015-12-15 Josh Crawmer

    I keep getting an error for "The text "Samsung Galaxy S II" was not found anywhere in the text of the current page."

    Here is my code:

    /**
    * @BeforeScenario @fixtures
    */
    public function loadFixtures()
    {
    $loader = new ContainerAwareLoader($this->getContainer());
    $loader->loadFromDirectory(__DIR__.'/../../src/AppBundle/DataFixtures');
    $executer = new OrmExecutor($this->getEntityManager());
    $executer->execute($loader->getFixtures(), true);
    }

    @fixtures
    Scenario Outline:
    When I fill in the search box with "<term>"
    And I press the search button
    Then I should see "<result>"
    Examples:
    | term | | result |
    | Samsung | | Samsung Galaxy S II |
    | XBox | | No products found |

    Everything has gone good up till this point. Please help!