Pimp your Behat Drupal Extension and rule the world

Antonio De Marco
19 Sep 2016
6 Comments
Antonio De Marco
19 Sep 2016
6 Comments
Antonio De Marco, 19 Sep 2016 - 6 Comments

Pimp your Behat Drupal Extension and rule the world

Make the most out of your Behat tests by using custom contexts, dependency injection and much more.

This post is an excerpt from the topics covered by our DrupalCon Dublin training: Drupal 8 Development - Workflows and Tools.

At Nuvole we consider writing good tests as a fundamental part of development and, when it comes to testing a complex site, there is nothing better than extensive behavioral tests using Behat. The benefits of such a choice are quite obvious:

  • Tests are very easy to write.
  • Behat scenarios serve as a solid communication mean between business and developers.

As a site grows in complexity, however, the default step definitions provided by the excellent Behat Drupal Extension might not be specific enough and you will quickly find yourself adding custom step to your FeatureContext or creating custom Behat contexts, as advocated by all official documentation.

This is all fine except that your boilerplate test code might soon start to grow into a non-reusable, non-tested bunch of code.

Enter Nuvole's Behat Drupal Extension.

Nuvole's Behat Drupal Extension

Nuvole's Behat Drupal Extension is built on the shoulders of the popular Behat Drupal Extension and it focuses on step re-usability and testability by allowing developers to:

  • Organize their code in services by providing a YAML service description file, pretty much like we all are used to do nowadays with Drupal 8.
  • Override default Drupal Behat Extension services with their own.
  • Benefit of many ready-to-use contexts that are provided by the extension out of the box.

Installation and setup

Install Nuvole's Behat Drupal Extension with Composer by running:

bash $ composer require nuvoleweb/drupal-behat

Setup the extension by following the Quick start section available on the original Behat Drupal Extension page, just use NuvoleWeb\Drupal\DrupalExtension instead of the native Drupal\DrupalExtension in your behat.yml as shown below:

default:
  suites:
    default:
      contexts:
        - Drupal\DrupalExtension\Context\DrupalContext
        - NuvoleWeb\Drupal\DrupalExtension\Context\DrupalContext
        ...
  extensions:
    Behat\MinkExtension:
      goutte: ~
      ...
    # Use "NuvoleWeb\Drupal\DrupalExtension" instead of "Drupal\DrupalExtension".
    NuvoleWeb\Drupal\DrupalExtension:
      api_driver: "drupal"
      ...
      services: "tests/my_services.yml"
      text:
        node_submit_label: "Save and publish"

"Service container"-aware Contexts

All contexts extending \NuvoleWeb\Drupal\DrupalExtension\Context\RawDrupalContext and \NuvoleWeb\Drupal\DrupalExtension\Context\RawMinkContext are provided with direct access to the current Behat service container. Developers can also define their own services by adding a YAML description file to their project and setting the services: parameter to point to its current location (as shown above).

The service description file can describe both custom services and override already defined services. For example, given a tests/my_services.yml containing:

services:
  your.own.namespace.hello_world:
    class: Your\Own\Namespace\HelloWorldService

Then all contexts extending \NW\D\DE\C\RawDrupalContext or \NW\D\DE\C\RawMinkContext will be able to access that service by just calling:

<?php
class TestContext extends RawDrupalContext {

 
/**
   * Assert service.
   *
   * @Then I say hello
   */
 
public function assertHelloWorld() {
   
$this->getContainer()->get('your.own.namespace.hello_world')->sayHello();
  }

}
?>

The your.own.namespace.hello_world service class itself can be easily tested using PHPUnit. Also, since Behat uses Symfony's Service Container you can list services your service depends on as arguments so to remove any hardcoded dependency, following Dependency Injection best practices.

Override existing services

Say that, while working on your Drupal 7 project, you have defined a step that publishes a node given its content type and title and you want to use the same exact step on your Drupal 8 project, something like:

Given I publish the node of type "page" and title "My page title"

The problem here is that the actual API calls to load and save a node differs between Drupal 7 and Drupal 8.

The solution is to override the default Drupal core services specifying your own classes in your tests/my_services.yml:

parameters:
  # Overrides Nuvole's Drupal Extension Drupal 7 core class.
  drupal.driver.cores.7.class: Your\Own\Namespace\Driver\Cores\Drupal7
  # Overrides Nuvole's Drupal Extension Drupal 8 core class.
  drupal.driver.cores.8.class: Your\Own\Namespace\Driver\Cores\Drupal8

services:
  your.own.namespace.hello_world:
    class: Your\Own\Namespace\HelloWorldService

You'll then delegate the core-specific business logic to the new core classes allowing your custom step to be transparently run on both Drupal 7 and Drupal 8. Such a step would look like:

<?php
class TestContext extends RawDrupalContext {

 
/**
   * @Given I publish the node of type :type and title :title
   */
 
public function iPublishTheNodeOfTypeAndTitle($type, $title) {
   
$this->getCore()->publishNode($type, $title);
  }

...
?>

Ready to use contexts

The extension also provides some utility contexts that you can use right away in your tests. Below a quick overview of what's currently available:

Context Description
NuvoleWeb\Drupal\DrupalExtension\Context\DrupalContext
Standard Drupal context. You want to use this one next to (and not instead of) Drupal\DrupalExtension\Context\DrupalContext.
NuvoleWeb\Drupal\DrupalExtension\Context\ContentContext
Perform operations on Content.
NuvoleWeb\Drupal\DrupalExtension\Context\CKEditorContext
Allows to interact with CKEditor components on your page.
NuvoleWeb\Drupal\DrupalExtension\Context\ResponsiveContext:
  devices:
    mobile_portrait: 360x640
    mobile_landscape: 640x360
    tablet_portrait: 768x1024
    tablet_landscape: 1024x768
    laptop: 1280x800
    desktop: 2560x1440
Resize the browser according to the specified devices, useful for testing responsive behaviors.
NuvoleWeb\Drupal\DrupalExtension\Context\PositionContext
Check position of elements on the page.
NuvoleWeb\Drupal\DrupalExtension\Context\ChosenFieldContext
Interact with Chosen elements on the page.

We will share more steps in the future enriching the current contexts as well as providing new ones so keep an eye on the project repository!

Disclaimer

At the moment only Drupal 8 is supported but we will add Drupal 7 support ASAP (yes, it's as easy as providing missing Drupal 7 driver core methods and adding tests).

Comments

Comments

Jonathan Shaw
19 Sep 2016

Fantastic, I look forward to trying it out, especially the additional contexts you provide. There's many common things that the Behat Drupal extension doesn't provide out of the box. You might be interested in https://github.com/jhedstrom/drupalextension/pull/300 which adds an EntityContext for working with any D8 content entities; file, media, commerce, custom, anything you like.

Fantastic, I look forward to trying it out, especially the additional contexts you provide. There's many common things that the Behat Drupal extension doesn't provide out of the box. You might be interested in https://github.com/jhedstrom/drupalextension/pull/300 which adds an EntityContext for working with any D8 content entities; file, media, commerce, custom, anything you like.

Jonathan Shaw, 19 Sep 2016

Fantastic, I look forward to trying it out, especially the additional contexts you provide. There's many common things that the Behat Drupal extension doesn't provide out of the box. You might be interested in https://github.com/jhedstrom/drupalextension/pull/300 which adds an EntityContext for working with any D8 content entities; file, media, commerce, custom, anything you like.

Jonathan Shaw, 19 Sep 2016
Antonio De Marco
20 Sep 2016

Thank you for the suggestion Jonathan, we will check that up!

Thank you for the suggestion Jonathan, we will check that up!

Antonio De Marco, 20 Sep 2016

Thank you for the suggestion Jonathan, we will check that up!

Antonio De Marco, 20 Sep 2016
Kevin Quillen
20 Sep 2016

This advancement is a great shot in the arm for people adopting Behat for testing. Drupal Behat Extension is great, is there any chance at merging efforts?

This advancement is a great shot in the arm for people adopting Behat for testing. Drupal Behat Extension is great, is there any chance at merging efforts?

Kevin Quillen, 20 Sep 2016

This advancement is a great shot in the arm for people adopting Behat for testing. Drupal Behat Extension is great, is there any chance at merging efforts?

Kevin Quillen, 20 Sep 2016
Antonio De Marco
20 Sep 2016

Thank you for your feedback Kevin! Indeed we could try to have merged in only the service container part and keep the rest as a collection of re-usable contexts.

Thank you for your feedback Kevin! Indeed we could try to have merged in only the service container part and keep the rest as a collection of re-usable contexts.

Antonio De Marco, 20 Sep 2016

Thank you for your feedback Kevin! Indeed we could try to have merged in only the service container part and keep the rest as a collection of re-usable contexts.

Antonio De Marco, 20 Sep 2016
Chris Roane
6 Apr 2017

Hi!

So we've used Behat with testing D7 sites, but we are wondering if we should continue to try to use behat with D8. The main question we have is why should we use Behat over phpUnit BrowserTestBase functionality that is in core? It seems like both options cover most of the same use cases?

Hi!

So we've used Behat with testing D7 sites, but we are wondering if we should continue to try to use behat with D8. The main question we have is why should we use Behat over phpUnit BrowserTestBase functionality that is in core? It seems like both options cover most of the same use cases?

Chris Roane, 6 Apr 2017

Hi!

So we've used Behat with testing D7 sites, but we are wondering if we should continue to try to use behat with D8. The main question we have is why should we use Behat over phpUnit BrowserTestBase functionality that is in core? It seems like both options cover most of the same use cases?

Chris Roane, 6 Apr 2017
Andrea Pescetti
5 Jun 2018

Comments on this post are now closed. Follow the project page for the latest updates.

Comments on this post are now closed. Follow the project page for the latest updates.

Andrea Pescetti, 5 Jun 2018

Comments on this post are now closed. Follow the project page for the latest updates.

Andrea Pescetti, 5 Jun 2018

Get your project started today!

Contact us