Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Context Organization and Behat Suites

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

When Behat loads, it reads step definitions from FeatureContext and MinkContext because of the behat.yml setup:

12 lines behat.yml
default:
suites:
default:
contexts:
- FeatureContext
- Behat\MinkExtension\Context\MinkContext
... lines 7 - 12

This is a really powerful idea: instead of having one giant context class, we can break things down into as many small, organized pieces. We might have one context for dealing with adding users to the database and another for the API. If you look at our FeatureContext, we already have two very different ideas mixed together: some functions interact with the terminal and others help deal with a web page.

This is begging to be split into 2 classes. Let's copy FeatureContext and create a new file called CommandLineProcessContext. Update the class name and get rid of anything in here that doesn't help do things with the command line:

... lines 1 - 9
class CommandLineProcessContext implements Context, SnippetAcceptingContext
{
private $output;
... lines 13 - 16
public function iHaveAFileNamed($filename)
{
touch($filename);
}
... lines 21 - 24
public function iRun($command)
{
$this->output = shell_exec($command);
}
... lines 29 - 32
public function iShouldSeeInTheOutput($string)
{
assertContains(
$string,
$this->output,
sprintf('Did not see "%s" in output "%s"', $string, $this->output)
);
}
... lines 41 - 44
public function moveIntoTestDir()
{
if (!is_dir('test')) {
mkdir('test');
}
chdir('test');
}
... lines 52 - 55
public function moveOutOfTestDir()
{
chdir('..');
if (is_dir('test')) {
system('rm -r '.realpath('test'));
}
}
... lines 63 - 66
public function iHaveADirNamed($dir)
{
mkdir($dir);
}
}

In FeatureContext, do the opposite: remove all the things that have nothing to do with working on a web site. Delete these functions and our before and after scenario hooks:

... lines 1 - 13
class FeatureContext extends RawMinkContext implements Context, SnippetAcceptingContext
{
... lines 16 - 22
public function __construct()
{
}
... lines 26 - 29
public function iFillInTheSearchBoxWith($term)
{
$searchBox = $this->assertSession()
->elementExists('css', 'input[name="searchTerm"]');
$searchBox->setValue($term);
}
... lines 37 - 40
public function iPressTheSearchButton()
{
$button = $this->assertSession()
->elementExists('css', '#search_submit');
$button->press();
}
... lines 48 - 51
private function getPage()
{
return $this->getSession()->getPage();
}
}

That's a lot clearer.

Of course to keep our tests passing, we need to tell Behat about our new context:

13 lines behat.yml
... lines 1 - 3
contexts:
- FeatureContext
- CommandLineProcessContext
- Behat\MinkExtension\Context\MinkContext
... lines 8 - 13

If we run behat now:

$ ./vendor/bin/behat

It should run all of our features: the ls and web stuff. It does, and it works! Ignore the undefined functions - those are from product_admin.feature: we haven't finished that yet.

Multiple Suites

But we can go further. in behat.yml, check out the suites key. Currently, we have one "suite" called default:

13 lines behat.yml
default:
suites:
default:
... lines 4 - 13

But you could have many. What's a suite? It's a combination of a set of feature files and the contexts that should be used for them. Think about it: the ls.feature is the only feature that needs CommandLineProcessContext. And every other feature only needs FeatureContext and MinkContext. This is the perfect use-case for a second suite that I'm going to call commands. In this case, only add the CommandLineProcessContext:

18 lines behat.yml
default:
suites:
default:
... lines 4 - 7
commands:
contexts:
- CommandLineProcessContext
... lines 11 - 18

Remove that from the default suite:

18 lines behat.yml
default:
suites:
default:
contexts:
- FeatureContext
- Behat\MinkExtension\Context\MinkContext
... lines 7 - 18

When you execute Behat, it uses the default suite unless you tell it which one to use with the --suite option. Try it with --suite=commands and then run ls.feature:

$ ./vendor/bin/behat --suite=commands features/ls.feature

Or you can use the -dl option to see only the definition lists associated with the contexts in that suite:

$ ./vendor/bin/behat --suite=commands features/ls.feature -dl

Without --suite, we see definitions for the default suite:

$ ./vendor/bin/behat -dl

And yes, we can go even further by telling each suites which features belong to them. Under the features/ directory, create two new directories called commands and web Let's organize: move ls.feature into commands/ and the other four features into web/. Now, add a paths key to the default suite and set it to [%paths.base%/features/web]:

18 lines behat.yml
default:
suites:
default:
contexts:
... lines 5 - 6
paths: [ %paths.base%/features/web ]
... lines 8 - 18

%paths.base% is a shortcut to the root of the project. For the commands suite, do the same thing to point to commands/:

18 lines behat.yml
default:
suites:
default:
... lines 4 - 7
commands:
... lines 9 - 10
paths: [ %paths.base%/features/commands ]
... lines 12 - 18

Now, if you run the default suite:

$ ./vendor/bin/behat

Behat knows to only execute the features in the web/ directory. With --suite=commands, it only runs the features inside of commands/:

$ ./vendor/bin/behat --suite=commands

So if you have two very different things that are being tested, consider separating them into different suites entirely. But at the very least, use multiple contexts to keep organized and stay sane.

Leave a comment!

2
Login or Register to join the conversation
Default user avatar
Default user avatar Johnatas Montezuma | posted 5 years ago

How to run default features and command features with a single line of command?

Reply

Hey Johnatas,

Try to run ./vendor/bin/behat without specifying --suite option. This way Behat will run all your features you have in behat.yml config file.

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

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
    }
}
userVoice