Skip to main content

Custom step definitions for Drupal with DrupalExtension and Behat

In a recent article on some issues we've encountered with Drupal, Behat and pathauto, we were asked to provide some examples of custom step definitions.

In this article we'll dive into how to create your own step definitions and the kind of thing you can do.

by lee.rowlands /

Background

Behat's natural gherkin language lends itself well to creating your acceptance criteria in it's Given/When/Then syntax.

So for this article we'll step through a fictional story. In this instance lets assume the site uses Media and File entity in conjunction with a WYSWIYG module.

The client requirement is that when they use the WYSIWYG dialog for Media module, that the default view mode selected is the 'Original' image.

So what would the acceptance criteria look like for that in Gherkin syntax?

Feature: Insert file
  As a content author  
So that images embed correctly
  The default view mode should be original
@api
Scenario: View mode defaults to Original
  Given I am logged in as a user with the "Content Author" role
  And I visit a file embed form
  Then the selected view mode should be original

So this is where Behat is cool, it helps you write the code you need.

Creating your FeatureContext.php file

So I'm assuming you already have Behat installed. Start by initializing your bootstrap and FeatureContext file.

./vendor/behat/behat/bin/behat --init

This will create a /features and /features/bootstrap folder along with /features/bootstrap/FeatureContext.php

Open up the file and change the class to extend DrupalContext, being sure to add the required use statement.

eg instead of

class FeatureContext extends BehatContext

have

class FeatureContext extends DrupalContext

and add

use Drupal\DrupalExtension\Context\DrupalContext

to the top of the file with the other use statements.

Run your test for the first time

Now because we're using either the drush or Drupal driver here, we already get the 'Given I am logged in as a user with the "foo" role' for free.

To see all the other free step definitions we have at our disposal

./vendor/behat/behat/bin/behat -dl

There's quite a few, which you can use for your step definitions, but this article is about custom definitions, so we'll focus on that.

So assuming the test above lives in a features/insert-file.feature we'd run

./vendor/behat/behat/bin/behat features/insert-file.feature

Because we haven't created definitions for our custom steps, Behat will output the code you need like (for example):

/**   
* @Given /^I visit a file embed form$/
 */
 public function iVisitAFileEmbedForm() {
  throw new PendingException(); 
}

So we copy and paste this into our FeatureContext.php file and start coding.

Working with Mink

Under the hood of Behat is Mink, I recommend familiarizing yourself with the Mink classes, its pretty powerful.

So consider the 'I visit a file embed form' the difficulty we have is that we're not sure of a file id. So we could use DrupalDriver and use a database query to find a file in our install, or we can get tricky with Mink and find a file id by crawling the dom.

So here's a commented version of how we can implement this step.


/**
   * @Given /^I visit a file embed form$/
   */
  public function iVisitAFileEmbedForm() {
    // So here we're grabbing the Mink session and visiting the file listing page.
    $this->getSession()->visit($this->locatePath('/admin/content/file'));
    // Here we're finding the first checkbox in the file listing page, which is conveniently keyed by file id.
    if ($file_link = $this->getSession()->getPage()->find('css', '.file-entity-admin-file-form table tbody tr:first-child td:first-child input[type=checkbox]')) {
      // So we found a checkbox for a file, get the value from it - which is the fid.
      $fid = $file_link->getAttribute('value');
      // Now head to the file-embed form media displays in the dialog.
      // Without JavaScript this is of course just a normal page.
      $this->getSession()->visit($this->locatePath('/media/' . $fid . '/format-form'));
    }
    else {
      // So things went wrong here and we couldn't find a file.
      // So we throw an Exception so the test fails.
      throw new \Exception('Could not find a media file at /admin/content/file');
    }
  }

So what remains here is to write the code to check that the right default is selected.

I'll leave that as an exercise to the reader, with one clue, you can use

   $this->getSession()->getPage()->find('css', 'option[selected=selected][value=media_original]');

To search the dom for the element you're after. If you don't find anything, then throw an exception and the test fails.

Using Drupal APIs

So if you have your behat.yml configured to use the Drupal api driver, you can just use standard Drupal api functions in your step definition code

So instead of using Mink to crawl the dom, you could have done

/**
   * @Given /^I visit a file embed form$/
   */
  public function iVisitAFileEmbedForm() {
    // Find a file id.
    if ($fid = db_select('file_managed', 'f')
      ->fields('f', array('fid'))
      ->execute()
      ->fetchField()) {
      // Now head to the file-embed form media displays in the dialog.
      // Without JavaScript this is of course just a normal page.
      $this->getSession()->visit($this->locatePath('/media/' . $fid . '/format-form'));
    }
    else {
      // So things went wrong here and we couldn't find a file.
      // So we throw an Exception so the test fails.
      throw new \Exception('Could not find a media file.');
    }
  }

Both achieve the same thing, it just depends on which you prefer.

Summary

So I hope that whets your appetite for Behat and custom step definitions.