Behat + Symfony 2 Secret: How To Subcontext MinkContext

Update: Check comments for solution. Thanks everzet.

Behat is a scenario framework. It means that you can describe scenarios for the interaction between user and the browser and use those as your acceptance criteria. A scenario is basically a telling of what is to happen (then), in which circumstances it is going to happen (given), and what is causing the thing that is going to happen (when).

Put it the other way around, in each scenario, and there can be as many as needed, there is a set of steps to be taken for setting things up (given), for acting things that cause things to happen (when), and for describing things that should happen (then).

Each scenario comprises several steps that are described in a human like language. The specification can then be run without a problem, however, there is no way to make the web application at hand what the words of a step mean unless they are tied to the browser on which the web application is running. The link between browser and human like language steps is called a step definition, step description, or is also called step contextualization. It contextualize the human steps by interpreting the human like language steps and determining the manner in which they are run into code that controls a browser client driver.

Behat allows to have multiple ways of interpreting steps. These ways or manners of interpreting are called contexts. So contexts hold rules for interpretation or also called step definitions. In order to set all the interpretation rules straight Behat allows for organization contextualizing and grouping certain rules for certain contexts and using them when needed.

The use case for multiple contextualization can be seen this way. Imagine an app that reacts differently for different type of users. Say the app behavior is similar to all, but the interaction is different. Some users like to do a lot of clicks on some things a certain way, using links a certain way, reset their password, or contact support through specific channels or even chat a certain way. Even more simple, imagine that a user uses the accessibility features of a site for blind users and the regular user use the regular features of a site for people that can see. These are two different contexts. A step can be for instance: “When I login”. Both user blind and no blind will login eventually. However, the interpretation of the same step will take different routes. One will touch the screen or type whereas perhaps the other will speak up or trigger some actions that end up in the same action, that is log in.

Mink comes with a context of its own premade and home cooked. Moreover Behat allows us to use multiple contexts as subcontexts from a top root FeatureContext file. This post is about showing how to make use of subcontexts and how to instantiate MinkContext as one of those subcontexts we can use.

So in our FeatureContext class we have the following:

class FeatureContext extends BehatContext
{
    public function __construct(array $parameters) {
        $this->useContext('clicking', new ClickingContext($parameters));
        $this->useContext('dropoff', new DropoffContext($parameters));
        $this->useContext('mink', new MyMinkContext());
 
    }

And we create a MyMinkContext class like:

class MyMinkContext extends MinkContext{
 // your step definitions here
}

Notice we are extending our homemade class from MinkContext. And we are using it as subcontext since we aliased it to ‘mink’ above. The usage of how to get to methods/steps defined in the context is shown elsewhere in other posts of this site and on the Behat documentation.

The idea shown here is that the organization for contexts and subcontexts fosters horizonal reusability before we get traits on php5.4. What this means in terms of application development? It means that one can organize contexts and run multiple context-based scenarios, do comparisons for certain usage cases through different patters of usage, etc.

Update: This warning is wrong.
Warning: It is important for reason of updating that you use Behat classes on your use clauses when coding. If you use BehatBundle or other can give you problems. So unless otherwise said, use the Behat classes.

Adapted explanation by everzet:
BehatBundle makes Behat to inject Kernel instead of parameters array into context constructor by default, whereas Behat creates context with array of context parameters.
In addition to this there is more information related to the use of methods for different contexts, especially the methods MinkContexts brings into the table. Therefore there are 3 ways of doing this:

1) add mink-related steps in MinkContext child
2) record newly created MinkContext in your FeatureContext instance variable, so you'll have access to it's methods (like getSession()) later
3) use getMainContext()->getContext('MINK_CONTEXT_ALIAS')->getSession

So here we give example of what we have been talking about implementing a nice aliasing taken from here:

<?php
 
namespace Acme\DemoBundle\Features\Context;
 
use Behat\BehatBundle\Context\BehatContext,
    Behat\Behat\Exception\PendingException;
use Behat\Gherkin\Node\PyStringNode,
    Behat\Gherkin\Node\TableNode;
 
/**
 * Feature context.
 */
class FeatureContext extends BehatContext
{
    /**
     * BehatBundle's main context constructor gets
     * KernelInterface instance as single parameter
     */
    public function __construct($kernel)
    {
        // Instantiate and use BehatBundle's MinkContext with provided $kernel
        $this->useContext('mink', new MyMinkContext($kernel));
    }
 
    /**
     * Get Mink session from MinkContext
     */
    public function getSession($name = null)
    {
        reutrn $this->getSubcontext('mink')->getSession($name);
    }
}

And the MyMinkContextClass here:

<?php
 
namespace Acme\DemoBundle\Features\Context;
 
use Behat\BehatBundle\Context\MinkContext as BaseMinkContext,
    Behat\Behat\Exception\PendingException;
use Behat\Gherkin\Node\PyStringNode,
    Behat\Gherkin\Node\TableNode;
 
/**
 * Mink-enabled context.
 */
class MyMinkContext extends BaseMinkContext
{
}

And here I just dump a very good explanation on all the details seen so far and problems I ran into:

Explanation: https://github.com/Behat/Mink/issues/68#issuecomment-1797567

If you use BehatBundle – use it’s contexts. If you don’t use BehatBundle – use Behat or Mink context as parent. Where’s the confusion???

The main difference between them is that BehatBundle’s contexts gets kernel instance injected into constructor instead of array of parameters. That’s all. BehatBundle provides extensions for both MinkContext and BehatContext which:

Gets kernel instead of array of parameters into __constructor()
Gets services out of service container (bound to kernel) instead of instantiating them by hands
Gets parameters out of service container (bound to kernel) instead of maintaining them by hands
That’s all.

So, if you use BehatBundle – extend Behat\BehatBundle\Context\BehatContext, it’ll give you access to kernel and container in child class. BUT don’t forget to provide kernel instance into it’s constructor.

Behat provides BehatContext with basic functionality
Mink provides MinkContext layer on top of the BehatContext functionality
BehatBundle overrides both BehatContext and MinkContext with kernel, container and services knowledge, so you can talk with your Symfony2 app through them
Also, in counterpart with Behat\BehatBundle\Context\BehatContext, which does nothing except defining some helper methods, Behat\BehatBundle\Context\MinkContext describes step definitions and hooks, needed for Mink. And as you remember – you can’t have 2 same definitions in suite. So, you can extend OR use Behat\*\Context\MinkContext only once!

Hope you have enjoyed this post, if you feel like so please donate. Thanks!

2 thoughts on “Behat + Symfony 2 Secret: How To Subcontext MinkContext

    • Thanks @everzet, it helped. Now that i have been working on hooking also phabric i review this step on defining MinkContext as a subcontext and now I understand better how this part works. How a class constructor is overridden and class extended and how kernel is passed and how parameters travel within the kernel. Thanks!

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>