Skip to main content

Drupal 8 Now: PHPUnit tests in Drupal 7

Drupal 8 comes with built-in support for PHP Unit for unit-testing, the industry standard for unit-tests.

But that doesn't mean you can't use PHP Unit for your testing and CI in Drupal 7, if you structure your code well.

Read on to find out what you need to do to use PHP Unit in Drupal 7.

by lee.rowlands /

Installing PHPUnit

The simplest way to install PHP Unit is via composer.

{
    "require-dev": {
        "phpunit/phpunit": "3.7.*"
    }
}

Then a

composer update "phpunit/phpunit" 

will get you the phpunit binary in /vendor/bin/phpunit

Autoloading

The next hurdle with PHP Unit testing Drupal 7 is getting the classes you need to auto-load. Because PHP Unit tests don't bootstrap Drupal, there is no registry and Drupal's built in .info file based class-loader doesn't work. Fortunately we can leverage Composer here. Composer has a classmap feature, which when nominated will cause Composer to scan a given folder and create a map of class-name to file-names for any class it finds inside that folder. For example to tell composer to discover all classes in your custom module inside your custom profile, you would nominate classmap like so:

"autoload": {
      "classmap": ["app/profiles/myprofile/modules/custom/mymodule/src/"]
  }

If you have several modules, just separate them with a comma, as seen in the example above, the classmap property is an array.

PHPUnit configuration

Now we have Autoloading out of the way, the next hurdle is bootstrapping PHP Unit, we could nominate the auto-loader file in each of our tests, but we don't need to, instead we can configure PHP Unit to use the autoloader automatically using a bootstrap script. PHP Unit knows to load its configuration from a file named phpunit.xml (or phpunit.xml.dist if you expect someone will need to change it). In here we tell PHP Unit where to find our tests, as well as how to bootstrap itself.

<!--?xml version="1.0" encoding="UTF-8"?-->

<phpunit colors="true" bootstrap="./vendor/autoload.php">
  <testsuites>
    <testsuite name="MyProfile">
        <directory>./app/profiles/myprofile/modules/custom/</directory>
        <directory>./tests/phpunit/</directory>
    </testsuite>
  </testsuites>
  <!-- Filter for coverage reports. -->
  <filter>
    <blacklist>
      <directory>./vendor</directory>
    </blacklist>
  </filter>
</phpunit>

We tell PHP Unit to look for the tests in a given directory, and exclude the vendor directory from code-coverage reports (which are a cool bonus).

Writing testable code

To be able to test your Drupal 7 with PHP Unit, you've got to write testable code. Whilst this might sound obvious, writing your code with unit-testing in mind, or even better - writing your unit tests first will improve the overall quality of your code - unit-testability will be an added bonus. For a primer on this, see the rest of our Drupal 8 now series.

Focussing on object-oriented code that can be auto-loaded certainly makes this easier however the majority of Drupal 7's APIs don't have object-oriented equivalents - so in order to unit test code that relies on these APIs we have to wrap those methods and use stub objects.

So for example, your code might need to load parents for a taxonomy term, in Drupal 7 (and unfortunately in Drupal 8 too - see this issue) this is achieved with the function taxonomy_get_parents_all but during our testing Drupal won't be bootstrapped so we won't have access to this function.

Lets say we're testing one of the plugins we talked about in this earlier article, instead of calling directly to taxonomy_get_parents_all, we create a new protected method called getParentsAll, and then inside that wrap taxonomy_get_parents_all(). The code looks like this.

class SomePlugin extends SomePluginBase {

/**
   * Wraps taxonomy_term_parents_all().
   *
   * @param \StdClass $term
   *   A term object
   *
   * @return array
   *   Array of term objects.
   */
  protected function getParents($term) {
    return taxonomy_get_parents_all($term->tid);
  }

}

So you might say this doesn't give us much, it's still a call out to procedural code from inside an object. But we can then stub this object.

class SomeTestPlugin extends SomePlugin {

/**
   * {@inheritdoc}
   */
  protected function getParents($term) {
    return array(
        (object) array('name' => 'foo', 'tid' => 1),
        (object) array('name' => 'bar', 'tid' => 2),
      );
  }

}

And then when we write our tests, we test SomeTestPlugin (the stub) instead of SomePlugin. But you might say this isn't testing taxonomy_get_parents_all, you're hard-coding its return - but that is the whole point of Unit Testing, testing an individual unit of code - we're not testing taxonomy_get_parents_all, we're testing how our object behaves in given scenarios.

Running your tests

Running your tests is as simple as running

./vendor/bin/phpunit

This will use your phpunit.xml file from the root of your project

Examples

For some simple example code, take a look at some of the tests in the Google DFP module on Drupal.org

More complex cases

For one of our projects we were tasked with building a scraper to handle scraping and submitting forms from a third-party service using Drupal (yes I know it sounds bizarre).

To achieve this we created a FormScraper object which used Goutte, Symfony's DomCrawler and Guzzle's HTTP client to fetch and scrape the external forms.

In order to test the Form object behaved as expected, we needed to mock the objects it interacted with to return representative values so we could test our object behaved as expected in those circumstances. To do this we used PHPUnit's mocking ability. In this example we want to mock the client (which does the actual HTTP requests) and wire it up to return a canned DomCrawler object with some dummy markup to test that our Form object is capable of transforming the given markup as expected. See the comments in the code for further explanation.

<?php

/**
 * @file
 * Contains \Drupal\mymodule\FormScraperTest
 */

namespace Drupal\mymodule;

use Symfony\Component\DomCrawler\Crawler;

/**
 * Class FormScraperTest
 * @covers Drupal\mymodule\FormScraper
 */
class FormScraperTest extends \PHPUnit_Framework_TestCase {

  protected $client;
  protected $scraperForm;

  /**
   * Sets up the test.
   */
  public function setUp() {
// Here we setup a mock scraper client.
    $this->client = $this->getMockBuilder('\Drupal\mymodule\ScraperClient')
      ->disableOriginalConstructor()
      ->getMock();

// Mock some markup.
$markup = 'some markup here'; // And wire up a new crawler to return that content.
  $form_crawler = new Crawler(null, 'http://example.com:8080/foo');
  $form_crawler->addHtmlContent($markup);

// Tell the mock client to return the crawler with the dummy content
// when its get method is called.
  $this->client->expects($this->once())
    ->method('get')
    ->will($this->returnValue($form_crawler)); // And when we create the form scraper pass it the mock client.
    $this->scraperForm = new FormScraper($this->client, 'http://example.com:8080/foo');
  }

// actual tests omitted..

}

Summary

PHPUnit is a very powerful test framework, it forces you to think about the best way to structure your code and once you have solid coverage enables more agile refactoring, let us know in the comments how you're using PHPUnit in Drupal 7.

Posted by lee.rowlands
Senior Drupal Developer

Dated

Comments

Comment by Michael Prasuhn

Dated

How do you manage your composer dependencies in a Drupal 7 project? How do you handle PSR4 autoloading as well?

Comment by lee.rowlands

Dated

Composer Manager will do the dependencies. PSR-4 can be done with either Composer Manager module or Xautoload module. See the earlier posts in this series.

Comment by Kevin

Dated

Use composer.json to manage dependencies in D7. It's simple enough.

Pagination