2015 Aug 13

Behavioral Driven Development in a Drupal context

First off, what is BDD? It stands for Behavioral Driven Development. In short, it's modeled after how users actually use your site or application. This is in contrast to traditional code driven testing à la PHPUnit or simpletest. Behat is the framework used with Drupal to do behavioral type testing. It is capable of testing several types of systems: terminal commands, REST APIs, etc. It is a PHP implementation of the Gherkin language, which powers Cucumber for Ruby. Gherkin is a human-esque language, kind of like SQL, in that you write out plainly which steps should happen to reach a desired result for a given scenario.

This method of testing is nice in a couple ways I can think of:

  • You can easily implement tests on an existing site.
  • The tests are fairly straight forward to imagine and write because you know what the site should do
  • They mirror user stories written for the how the site works, which you may have memorized or stashed somewhere.

Being on the "easier" side to setup is nice too as getting some tests written for a site that doesn't already have tests in place is always a good thing. When you start piling on functionality to an older site over time it's difficult to tell if anything is regressing (see The “okay, try now” loop)

I've been struggling with writing tests for an existing site, thinking "What are the things that I want to test that could break for users during say, a minor update of Drupal"?

That question as it turns out is easily answered if you can imagine how you normally "Test around" after such an update. First you may go to the homepage to make sure that loads and everything's cool. Phew! OK, that's good. What's next? Oh yeah, the site has that search feature we forgot about, we should probably make sure that's still working, and so forth. 

I'm sure everyone has that kind of feeling about updates. They can make you nervous in that you don't really know what just happened. By writing tests for the majority of the main top features of your site, you can run them more often than just for updating your site and faster as well. Say if on any given day you do some code changes over here, you want to make sure nothing gets affected over there, etc. Also, by getting some of the more menial testing out of the way, you can really put a microscope on the edge cases when you need to and save time on manually testing the same things over and over and potentially missing things.

Behat uses files called "Features" to run its tests. Features are broken down into scenarios within the feature. An example might be to have a user account feature that includes separate scenarios for logging in and logging out.

Here's an example for testing the typical stock drupal login that redirects you to the /user page on log in.

  User Accounts

  Log in to the site and see my account

  Given I am on the homepage
  And I fill in "Username" with "admin"
  And I fill in "Password" with "super_secret_pwd"
  And I press "Log in"
  Then I should see "log out"

To enable Behat to test web pages, you need to add Mink and a browser emulator. Mink functions as the connector between Behat and browser emulators, and provides a consistent testing API.

There are some commonly used browser emulators available such as Goutte, which is fast, but doesn't support JavaScript. Others, like Selenium and Firefox, are full-featured browsers, but will run more slowly.

Behat in a Drupal context usually means all three components being used together: Behat, Mink, and browser emulators.

Another key component is the Drupal extension for Behat which provides built in step definitions like "Given i am on X" and "Then I should see Y" specific to Drupal. From the module site:

The Drupal Extension is an integration layer between Behat, Mink Extension, and Drupal. It provides step definitions for common testing scenarios specific to Drupal sites.

Here's a basic rundown of the steps involved for getting setup:

1. Create a directory somewhere in your site to hold all things Behat. I put mine outside of the doc root (src) at the root of my git repo as I use a vagrant setup that looks like:


Having behat tests within sites/all/tests or similar can be done as well, but some of the path names inside the vendor directory can get long with version control and cause problems so your mileage may vary.

2. Create a composer.json file inside the Behat dir with the following:

  "require": {
    "drupal/drupal-extension": "~3.0"
  "config": {
    "bin-dir": "bin/"

3. Install the Behat Drupal extension and all dependencies with Composer. Run 'composer install' inside the Behat dir next to the composer.json file.

4. Now that composer has downloaded everything, you'll see a bin dir next to the new vendor dir. Run bin/behat --init to get the features dir setup.
 - the output of this gives you some hints:

    +d features - place your *.feature files here
    +d features/bootstrap - place your context classes here
    +f features/bootstrap/FeatureContext.php - place your definitions, transformations and hooks here

5. copy behat.yml.dist from vendor/drupal/drupal-extension and place it in at the root of your behat dir next to the bin and vendor directories. Rename it to behat.yml. It has placeholders in there for D8, D7, D6 and the Drush driver. There's a default at the top you can use as well. Typically though you can delete all but one entry and use that. Here's what mine looks like: 

        - FeatureContext
        - Drupal\DrupalExtension\Context\DrupalContext
        - Drupal\DrupalExtension\Context\MinkContext
        - Drupal\DrupalExtension\Context\MessageContext
        - Drupal\DrupalExtension\Context\DrushContext
      goutte: ~
      selenium2: ~
      base_url: http://localhost
      blackbox: ~
      api_driver: 'drupal'
        drupal_root: '/vagrant/src'
        username_field: "E-mail"
        log_out: "Logout"

**TIP, notice the "text" entry? That's for overriding the built in strings that come with the Drupal Extension. Recently I installed some modules that changed the login field label from Username to E-mail. I then needed to tell behat about that otherwise it would choke saying it couldn't find a field called "Username" at /user. I suppose you could also use the field name as well, which in this case wouldn't have changed, but it's nice to reflect something that did change during development within the test, i.e. the form label (in case it regresses).

Now you can run bin/behat -dl to see all the built in steps that you can start using. bin/behat -di will give you more verbosity about each step, such as params the steps take and the PHP class from which it originates.

Finally, to run an actual test, place a file called *.feature (mine's called login.feature) in the features dir with the contents similar to what I mentioned above with the feature example. Here's that same feature running on a stock D7 site with the login block in the sidebar.

  User Accounts

  Scenario:                               # features/login.feature:4
    Log in to the site
    Given I am on the homepage            # Drupal\DrupalExtension\Context\MinkContext::iAmOnHomepage()
    And I fill in "Username" with "admin" # Drupal\DrupalExtension\Context\MinkContext::fillField()
    And I fill in "Password" with "admin" # Drupal\DrupalExtension\Context\MinkContext::fillField()
    And I press "Log in"                  # Drupal\DrupalExtension\Context\MinkContext::pressButton()
    Then I should see "log out"           # Drupal\DrupalExtension\Context\MinkContext::assertPageContainsText()

1 scenario (1 passed)
5 steps (5 passed)
0m1.17s (41.58Mb)

That covers the basic implementation. If you need to test Javascript, you'll need selenium installed. There's some good info about how to set that up in the related reading links below.

Conclusion and related reading

Some more helpful info from around the web about Behat both specific to Drupal and just in general.

Up and Running with Behat, Drupal, & Vagrant
Basic behavior testing with Behat in Drupal
Behat Drupal Extension
Getting Started with Behat
BDD, Behat, Mink & other Wonderful Things (Preview)