Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Behat Loves Mink (Free Definitions from MinkExtension)

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

Behat parses scenarios and Mink is really good at browsing the web. If we combine their powers, we could start having steps that look a lot like what we have in search.feature.

... lines 1 - 5
Scenario: Search for a word that exists
Given I am on "/"
When I fill in "searchTerm" with "Samsung"
And I press "search_submit"
Then I should see "Samsung Galaxy S II"

For the Background step, we now know we could create a matching definition in FeatureContext and easily use Mink's session object to actually go to that URL. But earlier when we ran this scenario, it worked... so there must already be something tie'ing Behat and Mink together.

Let's see what's happening.

Free Behat Steps

First, in FeatureContext I had you extend MinkContext. Remove that now:

... lines 1 - 12
class FeatureContext implements Context, SnippetAcceptingContext
... lines 14 - 86

When we run Behat, it needs to know all of the step definition language that's available. You can see that list by passing a -dl to the Behat command:

vendor/bin/behat -dl

This shows the four ls definitions we built. So, Behat opens the FeatureContext class, parses out all of the @Given, @When and @Then annotations, and prints a final list here for our enjoyment.

When we add more step definitions, this list grows. And if we use something that isn't here yet, Behat very politely prints out the function for us in the terminal.

In behat.yml we added this MinkExtension configuration:

12 lines behat.yml
default:
... lines 2 - 6
extensions:
Behat\MinkExtension:
... lines 9 - 12

This library ties Behat and Mink together and gives us two cool things. First, it lets us access the Mink Session object inside of FeatureContext. We'll see that soon.

For the second thing, add a new config called suites: and a key under that called default: with a contexts: key. We'll talk about suites later. Under contexts, pass FeatureContext and Behat\MinkExtension\Context\MinkContext:

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

Now, Behat will look inside FeatureContext and MinkContext for those definition annotations.

Let's see what that gives us: run behat with the -dl option again:

vendor/bin/behat -dl

Boom! Now we see a huge list! These include definitions for all common web actions, like When I go to or When I fill in "field" with "value". This includes the stuff we're using inside of search.feature:

... lines 1 - 5
Scenario: Search for a word that exists
Given I am on "/"
When I fill in "searchTerm" with "Samsung"
And I press "search_submit"
Then I should see "Samsung Galaxy S II"

So that's why that scenario already worked.

Let's take a look at where these come from. I'll use shift+shift and search for MinkContext:

... lines 1 - 21
class MinkContext extends RawMinkContext implements TranslatableContext
{
/**
... lines 25 - 26
* @Given /^(?:|I )am on (?:|the )homepage$/
* @When /^(?:|I )go to (?:|the )homepage$/
*/
public function iAmOnHomepage()
{
$this->visitPath('/');
}
... lines 34 - 75
/**
... lines 77 - 78
* @When /^(?:|I )press "(?P<button>(?:[^"]|\\")*)"$/
*/
public function pressButton($button)
{
$button = $this->fixStepArgument($button);
$this->getSession()->getPage()->pressButton($button);
}
... lines 86 - 485
}

This looks just like our FeatureContext, but has a bunch of goodies already filled in.

So, why did I use this exact language inside of my scenario originally? Because, I'm lazy, and I knew if I followed the language here, I'd get all this functionality for free. And I'm from the midwest in the US: we love free things.

I'll take off the @javascript line:

... lines 1 - 4
Scenario: Search for a word that exists
... lines 7 - 11

Since we don't need JavaScript, and now we should be able to run our search feature. Perfect!

Leave a comment!

6
Login or Register to join the conversation
Default user avatar

Hi. I removed extends MinkContext and added Behat\MinkExtension\Context\MinkContext but when I ran my script again I get a undefined method FeatureContext::getSession() could you help explain why?

Reply
Default user avatar

ah! never mind ... i needed RawMinkContext! that way i can use $this->getMink()->getSession();

Reply

Nice work! RawMinkContext is the real important guy - it extends MinkContext, which just gives you the free definitions.

Cheers!

Reply
Default user avatar

Hello, there's a mistake that you are showing the FeatureContext.php file in the script instead of behat.yml when configuring the default suite :)

Reply

Ah, you're right! Thanks for pointing that out. I created an issue and we should fix it soon :). https://github.com/knpunive...

Thanks!

Reply

Thank you for this issue! Already fixed in 640dfc0 and published.

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