david winter

Using Behat with Mink

This is my second blog post on how to use Behat for your web application testing. If you haven’t already, be sure to read Getting started with Behat as this post continues on where that left off.

In my last post, I left you with hopefully enough knowledge to get started with some basic testing that comes for free, out of the box, by just installing Behat and Mink. You were able to do that because Mink comes with around 34 bundled definitions.

We created a very basic test to ensure that users were able to log into our example website:

Feature: User sessions
	In order to access their account
	As a user
	I need to be able to log into the website

	Scenario: Login
		Given I am on "/"
			And I should see "Login"
		When I fill in "email" with "[email protected]"
			And I fill in "password" with "mysecurepassword"
			And I press "Login"
		Then I should be on "/dashboard"
			And I should see "Welcome back"

	Scenario: Logout
		Given I am on "/dashboard"
		When I follow "Logout"
		Then I should be on "/"
			And I should see "Login"

So that we can work on this further, I’ve created a basic PHP script that we can use to play with. It’s written with the Silex micro-framework and doesn’t require any modification in order for us to write the tests in this blog post.

If you do want to play along, clone the example from github, setup a virtual host. Then just be sure to update the behat.yml file so that the base_url points to your new virtual host.

If you run bin/behat now, it’ll run through the test and fail:

2 scenarios (1 passed, 1 failed)
11 steps (8 passed, 2 skipped, 1 failed)

In our test, we’ve missed something! For the second scenario, we’ve not logged in the user! So they’re unable to access the /dashboard.

If we focus on just the Logout scenario from here on, in order to log in the user, we could do something like this:

Scenario: Logout
	Given I am on "/"
		And I should see "Login"
	When I fill in "email" with "[email protected]"
		And I fill in "password" with "mysecurepassword"
		And I press "Login"
		And I should be on "/dashboard"
	When I follow "Logout"
	Then I should be on "/"
		And I should see "Login"

This is basically duplicating the steps from the above login scenario. Running this, the tests now pass. Excellent. Are we finished? No. The test above isn’t very tidy because we just copy and pasted code. It’d be so much nicer if we could write something like:

Scenario: Logout
	Given I am logged in as "[email protected]" with password "mysecurepassword"
		And I am on "/dashboard"
	When I follow "Logout"
	Then I should be on "/"
		And I should see "Login"

That makes our feature file a lot more readable, cleaner, and also provides us with a new custom definition that we can then use in other future tests. How do we make this work?

When you run bin/behat and you’ve added a custom definition, it’s very kind by providing you with an example template for creating the PHP code to have this new definition work:

/**
 * @Given /^I am logged in as "([^"]*)" with password "([^"]*)"$/
 */
public function iAmLoggedInAsWithPassword($argument1, $argument2)
{
    throw new PendingException();
}

What do we do with this? Paste it directly into features/bootstrap/FeatureContext.php.

Now we just need to use Mink to program the definition. Looking at the source for the base mink context, we can use the following methods to complete the definition:

/**
 * @Given /^I am logged in as "([^"]*)" with password "([^"]*)"$/
 */
public function iAmLoggedInAsWithPassword($email, $password)
{
	$this->visit('/');
	$this->fillField('email', $email);
	$this->fillField('password', $password);
	$this->pressButton('Login');
}

Saving, and then running bin/behat again, the tests will now pass. And you’ve just created your first custom definition.

There is one final change we could make to this. As our definition is using all existing definitions, we could future proof it by changing it to the following:

/**
 * @Given /^I am logged in as "([^"]*)" with password "([^"]*)"$/
 */
public function iAmLoggedInAsWithPassword($email, $password)
{
	return array(
		new Step\Given('I am on "/"'),
		new Step\When('I fill in "email" with "'.$email.'"'),
		new Step\When('I fill in "password" with "'.$password.'"'),
		new Step\When('I press "Login"'),
	);
}

In order to use the above, be sure to update the use statement at the top of the file so it contains the last line of the following:

use Behat\Behat\Context\ClosuredContextInterface,
	Behat\Behat\Context\TranslatedContextInterface,
	Behat\Behat\Context\BehatContext,
	Behat\Behat\Exception\PendingException,
	Behat\Behat\Context\Step;

That’s all there is to it when creating custom definitions. The Mink source is a great way to look at how the out-of-the-box definitions work.

January 13, 2012

Questions? Comments? Your 2¢'s? → @davidwinter