Writing your first PHPUnit test in Drupal

In my previous post I wrote some typical Drupal code that lists nodes containing a given word in their title. In some upcoming posts I’ll rewrite that module using Test Driven Development and see whether the module turns out differently. But to get started with TDD in Drupal we need to be able to write our first test. Before we can even do that we need to install PHPUnit. Once you have PHPUnit installed, test that it is working properly by writing a trivial test file called FirstTest.php somewhere on your hard drive:

<?php
class FirstTest extends PHPUnit_Framework_TestCase
{
  public function test_two_plus_two_is_four()
  {
    $this->assertEquals(2+2, 4);
  }
}
?>

To run this you should use PHPUnit as follows from the same folder containing the test file:

$ phpunit FirstTest
PHPUnit 3.2.21 by Sebastian Bergmann.
.
Time: 0 seconds
OK (1 test)

If you get errors running this command then review the install instructions for PHPUnit and install it again if necessary.

Next: how do we apply PHPUnit to Drupal? Let’s create a new module folder, and an “info” file in it so Drupal knows what our new module is called: “tdd” for example. So we create a folder drupal/modules/tdd and a tdd.info file in this folder that contains:

name = tdd
description = Module written with TDD
package = TDD Demo Modules
version = VERSION
core = 6.x

Now let’s write our first test. PHPUnit convention is that if you have a PHP class called XYZ, you would write a test class for it called XYZTests, and by default place the test class in XYZTests.php in the same folder. Since we’re using Drupal and don’t have any object oriented code, let’s just call our test class TddTests, named after our new module, and put it into a new file in the same folder called TddTests.php. It needs to be a subclass of PHPUnit_Framework_TestCase like this:

<?php
class TddTests extends PHPUnit_Framework_TestCase
{
  public function test_tdd_help()
  {
    $this->assertEquals(
      tdd_help('admin/content/tdd'), "<p>Help for TDD module.</p>");
  }
}
?>

This test will call our new module’s “help” function with the path to the module’s new page and make sure we get the proper help. This seems like a good first test to write, since it’s very simple but still proves that we can call Drupal code from PHPUnit. Let’s run it and see what happens… But first you have to cd to the folder containing the TddTests.php file. PHPUnit also assumes that by default you’re executing the tests from the folder containing the test file. Let's try running our test:

$ cd modules/tdd
$ phpunit TddTests.php 
PHPUnit 3.2.21 by Sebastian Bergmann.
Fatal error: Call to undefined function tdd_help() in
/Users/pat/htdocs/drupal/modules/tdd/TddTests.php on line 6

Obviously the test fails because we haven’t written our new module yet. This seems like a waste of time, but we've taken the first step in the TDD cycle: write a failing unit test. Now let's start writing tdd.module:

<?php
function tdd_help($path, $arg) {
  switch ($path) {
    case 'admin/content/tdd':  
      return '<p>Help for TDD module.</p>';
  }
}
?>

If you run this test again you’ll get the same error message. So now what’s wrong? We forgot to enable our new module in the Drupal admin console, so the “tdd_help” function is still undefined. To enable it, open the Drupal admin console, go to the Administer->Site Building->Modules page and look at the bottom for a section called “TDD Demo Modules.”

But now if you run PHPUnit you’ll still get the same error. The mostly empty “TDD” module is now setup and working inside of Drupal, but PHPUnit has no idea what Drupal is or how to load it. What we need to do is declare somehow in TddTests.php to include and initialize the Drupal framework. To get this to work, we can use the index.php file in the root folder of Drupal app as an example. This is the PHP file that handles all incoming requests to Drupal by initializing the framework, executing the proper menu callback and displaying the result. If you look at the top of the index.php file, you’ll see these two lines:

require_once './includes/bootstrap.inc';
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);

If we copy these 2 lines into TddTests.php like this:

<?php
require_once './includes/bootstrap.inc';
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
class TddTests extends PHPUnit_Framework_TestCase
{
  public function test_tdd_help()
  {
    $this->assertEquals(
      tdd_help('admin/content/tdd'), "<p>Help for TDD module.</p>");
  }
}
?>

… and try to run our test again we get:

$ phpunit TddTests
Warning: require_once(./includes/bootstrap.inc): failed to open stream:
No such file or directory in /Users/pat/htdocs/drupal/modules/tdd/TddTests.php on line 2
Fatal error: require_once(): Failed opening required
'./includes/bootstrap.inc'
(include_path='.:/usr/local/php5x/lib/php') in
/Users/pat/htdocs/drupal/modules/tdd/TddTests.php on line 2

The problem now is that the include line has the wrong relative path. If you corrected it you would still get more errors trying to include other Drupal files. It turns out that the drupal_bootstrap function assumes that the current directory is set to the root folder of your app: the location of index.php. To get it all to work, we just need to execute the tests from the root folder:

$ cd ~/htdocs/drupal
$ phpunit modules/tdd/TddTests.php 
PHPUnit 3.2.21 by Sebastian Bergmann.
Class modules/tdd/TddTests could not be found in modules/tdd/TddTests.php.

This last error appears because we aren’t using the PHPUnit command line properly. The first parameter is the test class name; the second optional parameter that we need to use now is the test file path. Here’s the proper command line to use:

$ phpunit TddTests modules/tdd/TddTests.php 
PHPUnit 3.2.21 by Sebastian Bergmann.
.
Time: 0 seconds
OK (1 test)

Finally we’ve successfully written and executed our first test! More than that, this is our first step toward writing a Drupal module using TDD. Next time I’ll get to work writing the rest of the module using TDD with these files as a starting point.