Handling Authentication when Using Behat & Mink

Author: Lewis
Tuesday, April 22 2014

We use a combination of Behat, Mink and Sauce to make sure all of our applications are automatically and continually rested. If you’ve ever suffered from ‘deployment dread’ (the feeling of fear that occurs for a few hours after deploying your code), it’s probably because you don’t have confidence in your automated testing - or worse - you don’t have any. We’ve tried a number of tools and processes to reduce our deployments dread, but none have cured our fear as much as Behat.

One common problem when testing applications, especially the Symfony-powered, back-office management systems that we specialise in, is that often you'll be testing areas with restricted access (i.e. behind a login page).

A common mistake that developers often make is designing their stories or testing scenarios to continue from a previous scenario or state. This means logging in once and then assuming the subsequent tests will continue from the logged in state. This won’t work with Behat, since it forces context isolation. This means that not only will you need to Bootstrap the state that you expect your application to be in, but you’ll also need Bootstrap the state of the client (in our case, the browser). If every page is behind a secure login, that means you’ll need to login for every test.

This will make for quite lengthy and messy tests, since each test will end up looking something like this:

Scenario: Show list of customers
    Given I am on "/customers" 
    When I fill in the following: 
     | Login | Admin | 
     | Password | MyPassword | 
    And press "Login" 
    Then I should see "List of customers"

There’re four lines that are both duplicated between our scenarios, and not particularly relevant to the feature we’re describing. Don’t fear, though, there’s a simple and much cleaner solution to this which involves creating our own step in our FeatureContext:

/**
* @Given /^I am authenticated as "([^"]*)" using "([^"]*)"$/
*/
public function iAmAuthenticatedAs($username, $password) {
    $this->visit('/login');
    $this->fillField('username', $username);
    $this->fillField('password', $password);
    $this->pressButton('Sign in');
}

These could also be expressed as meta-steps, although we’re not a huge fan of the idea of placing steps inside a context (and neither is the developer behind Behat by the sounds of it).

Using this step, your new scenario should now look like the following:

Scenario: Show list of customers
    Given I am on "/customers"
    And I am authenticated as "Admin" using "MyPassword" 
    Then I should see "List of customers"

Which is already looking much cleaner.

Although we’ve simplified our scenarios by abstracting the details of how we authenticate, we’re still having to specify the admin user’s password in each scenario. This seems like something that isn’t specific to each scenario, we don’t care what the user’s password is, only that they are the current logged-in user. Luckily, Behat let’s us define ‘untitled’ scenarios called ‘Backgrounds’. We can use these to specify a basic fixture of users, which we can then use in our scenarios.

Let’s start by adding our background scenario, before our other scenarios:

Background:
    Given there are following users:
     | username | password   |
     | Admin    | MyPassword |

Now we can write this step into our ‘FeatureContext’:

private $users = array();

/**
* @Given /^there are following users:$/
*/
public function thereAreFollowingUsers(TableNode $table) {
    foreach ($table->getHash() as $row) {
        $this->users[$row['username']] = $row;
    }
}

You can probably tell from just reading the code, that all this does is store the table as a hash in a private property, indexed by the username. Now we just need to update our ‘iAmAuthenticAs’ step to reference the hash, and find the user’s password automatically:

/**
* @Given /^I am authenticated as "([^"]*)"$/
*/
public function iAmAuthenticatedAs($username) {
    if (!isset($this->users[$username]['password'])) {
        throw new \OutOfBoundsException('Invalid user '. $username);
    }

    $this->visit('/login');
    $this->fillField('username', $username);
    $this->fillField('password', $this->users[$username]['password']);
    $this->pressButton('Sign in');
}

Now we can finally update our scenario by removing the password from it:

Scenario: Show list of customers
    Given I am on "/customers"
    And I am authenticated as "Admin"
    Then I should see "List of customers"

One thing that we use ‘@Background’ scenarios for, is specifying data fixtures. Data fixtures are a way of Bootstrapping your database so that it is in a state specifically made for testing your feature. In our example, you could use your ‘thereAreTheFollowingUsers’ step to make sure the users exist in your application, inserting them if they don’t.

In a future blog, we’ll show you how to integrate ‘Alice’, a popular fixtures library, into your background scenarios so that you can achieve this with minimal effort or duplication.

PHPStorm File Watcher for PHPSpec Updating Entities When an Insert Has a Duplicate Key in Doctrine ORM