Fork us

Testing Features - FeatureContext Class

We’ve already used this strange FeatureContext class as a home for our step definitions and hooks, but we haven’t done much to explain what it actually is.

The Context class is a simple POPO (Plain Old PHP Object) used by Behat to represent testing part of your features suite. If *.feature files are all about describing how your application behaves, then the context class is all about how to test your application, and that it actually behaves as expected. That’s it!

# features/bootstrap/FeatureContext.php
<?php

use Behat\Behat\Context\BehatContext;

require_once 'PHPUnit/Autoload.php';
require_once 'PHPUnit/Framework/Assert/Functions.php';

class FeatureContext extends BehatContext
{
    public function __construct(array $parameters)
    {
        $this->useContext('subcontext_alias', new AnotherContext());
    }

    /** @BeforeFeature */
    public static function prepareForTheFeature()
    {} // clean database or do other preparation stuff

    /** @Given /^we have some context$/ */
    public function prepareContext()
    {} // do something

    /** @When /^event occurs$/ */
    public function doSomeAction()
    {} // do something

    /** @Then /^something should be done$/ */
    public function checkOutcomes()
    {} // do something
}

Context Class Requirements

In order to be used by Behat, your context class should follow 3 simple rules:

  1. Context class should implement Behat\Behat\Context\ContextInterface or extend base class Behat\Behat\Context\BehatContext (as in the previous example).
  2. Context class should be called FeatureContext. It’s a simple convention inside the Behat infrastructure.
  3. Context class should be findable and loadable by Behat. That means you should somehow tell Behat about your class file. The easiest way to do this is to put your class file inside the features/bootstrap/ directory. All *.php files in this directory are autoloaded by Behat before any feature is run.

Note

By convention, the context class should be called FeatureContext, but this could be easily changed through the cli configuration.

The easiest way to start using Behat in your project is to call behat with the --init option inside your project directory:

$ behat --init

Behat will create a few directories and a skeleton FeatureContext class inside your project:

../_images/--init.png
# features/bootstrap/FeatureContext.php
<?php

use Behat\Behat\Context\ClosuredContextInterface,
    Behat\Behat\Context\TranslatedContextInterface,
    Behat\Behat\Context\BehatContext,
    Behat\Behat\Exception\PendingException;
use Behat\Gherkin\Node\PyStringNode,
    Behat\Gherkin\Node\TableNode;

/**
 * Features context.
 */
class FeatureContext extends BehatContext
{
}

Contexts Lifetime

Your context class is initialized before each scenario runs, and every scenario has its very own context instance. This means 2 things:

  1. Every scenario is isolated from each other scenario’s context. You can do almost anything inside your scenario context instance without the fear of affecting other scenarios - every scenario gets its own context instance.
  2. Every step in a single scenario is executed inside a common context instance. This means you can set private instance variables inside your @Given steps and you’ll be able to read their new values inside your @When and @Then steps.

Using Subcontexts

At some point, it could become very hard to maintain all your step definitions and hooks inside a single class. You could use class inheritance and split definitions into multiple classes, but doing so could cause your code to become more difficult to follow and use.

In light of these issues, Behat provides a more flexible way to help make your code more reusable: using one or more contexts inside your main context. A context used from within another context is called a subcontext:

# features/bootstrap/FeatureContext.php
<?php

use Behat\Behat\Context\BehatContext;

class FeatureContext extends BehatContext
{
    public function __construct(array $parameters)
    {
        $this->useContext('subcontext_alias', new SubContext());
    }
}

Note

PHP does not yet support horizontal reusability in its core feature set. While this functionality, called traits, is on the roadmap for PHP 5.4, Behat provides subcontexts as a stop-gap solution to achieve horizontal reusability until this functionality is available in a stable PHP release.

Behat\Behat\Context\BehatContext provides a special useContext() instance method allowing you to connect one or more subcontext instances to your main FeatureContext class.

The first argument to the useContext() method is always a subcontext alias (subcontext_alias), allowing you to later access any subcontext from another subcontext.

SubContext instances should follow the same Context Class Requirements as your main FeatureContext:

#features/bootstrap/SubContext.php
<?php

use Behat\Behat\Context\BehatContext;

class SubContext extends BehatContext
{
    public function __construct(array $parameters)
    {
        // do subcontext initialization
    }
}

All step definitions and hooks defined in a subcontext are parsed by Behat and available right away to use in your features.

If you need to inject parameters or make other changes to your subcontext object, do so before passing it into useContext():

# features/bootstrap/FeatureContext.php
<?php

use Behat\Behat\Context\BehatContext;

class FeatureContext extends BehatContext
{
    public function __construct(array $parameters)
    {
        $this->useContext('subcontext_alias', new SubContext(array(
            /* custom params */
        )));
    }
}

Communications Between Contexts

Sometimes you might need to call a specific context method or attribute from within another context. BehatContext has two methods to accomplish this:

  1. getMainContext() - returns the main context instance in which all other contexts are used.
  2. getSubcontext($alias) - returns a subcontext given its alias, which was defined when it was passed to useContext().

Keeping this in mind, you can always call any context method using the following statement:

$this->getMainContext()->getSubcontext('subcontext_alias')->some_method();

Creating Your Very Own Context Class

The easiest way to start with Behat is to just extend the base class Behat\Behat\Context\BehatContext. But what if you don’t want to inherit from another class? Then you should create your own context class.

To use your custom class as a Behat context, it must implement a simple interface:

<?php

namespace Behat\Behat\Context;

interface ContextInterface
{
    function getSubcontexts();
    function getSubcontextByClassName($className);
}

This interface actually only has 2 methods:

  • getSubcontexts() - should return an array of subcontext instances (if it even has any).
  • getSubcontextByClassName() - should return a subcontext instance given its class name. This method is used to ensure your subcontext definitions will always be called inside the proper context instance.

Your custom FeatureContext class could look like this:

# features/bootstrap/FeatureContext.php
<?php

use Behat\Behat\Context\ContextInterface;

class FeatureContext implements ContextInterface
{
    private $subcontext;

    public function __construct()
    {
        $this->subcontext = new SubContext();
    }

    public function getSubcontexts()
    {
        return array($this->subcontext);
    }

    public function getSubcontextByClassName($className)
    {
        if ('SubContext' === $className) {
            return $this->subcontext;
        }
    }
}