3 Easy Steps to Drupal Unit Tests

While working on the SPS Module we became very well acquainted with Drupal's Unit Test Class. I will have a follow up Post on the lessons we learned about writing test, but here I will outline the basic steps for adding unit test to a module. It is worth noting that I will be talking about the Drupal Unit test not the Drupal Web Test.

Erik Summerfield, Director of Engineering
#Development | Posted

While working on the SPS Module we became very well acquainted with Drupal's Unit Test Class. I will have a follow up Post on the lessons we learned about writing test, but here I will outline the basic steps for adding unit test to a module. It is worth noting that I will be talking about the Drupal Unit test not the Drupal Web Test.

Step 1 - Tell Drupal about your tests

Drupal will look for classes that define tests ,so all we need to do is make sure that those classes can be found. To do this we just add a files line to the .info file, so that Drupal knows which files to investigate for test classes.

MODULE.info

  1.   name = MODULE
  2.   core = 7.x
  3.   files[] = tests/*.test

Now we can put all of our tests into the tests folder of the module, as long as we add .test to the file name.

Step 2 - Test Base Class

While a base class is not needed, it can be very helpful, as there are things (such as enabling your module) that can be done in a setup method.
tests/MODULEBase.test

  1. abstract class MODULEBaseUnitTest extends DrupalUnitTestCase {
  2.   /**
  3.    * One using of this function is to enable the module used for testing, any dependencies
  4.    * or anything else that might be universal for all tests
  5.    */
  6.   public function setUp() {
  7.     parent::setUp();
  8.     //enable module
  9.     $this->enableModule('MODULE');
  10.  
  11.     // enable dep and other thing for all tests
  12.   }
  13.  
  14.   /**
  15.    * Fake enables a module for the purpose of a unit test
  16.    *
  17.    * @param $name
  18.    *  The module's machine name (i.e. ctools not Chaos Tools)
  19.    */
  20.   protected function enableModule($name) {
  21.     $modules = module_list();
  22.     $modules[$name] = $name;
  23.     module_list(TRUE, FALSE, FALSE, $modules);
  24.   }
  25.   ...
  26. }

In the code sample, we simply create a base class that extends DrupalUnitTestCase, and add a setup method, to take care of anything that needs to be done before all of our tests. We also include an enableModule method that fakes enabling a module (the DrupalUnitTestCase, does not have access to a database, so enabling a module the normal way is not available).

Other things we can add to the base case are new methods to support our tests (such as asserts) that one might want to use over and over. For example in SPS module we add an assertThrows, which tests to ensure the correct exception is thrown (note this assert only works in PHP 5.3)
tests/MODULEBase.test

abstract class MODULEBaseUnitTest extends DrupalUnitTestCase {

  1.   ...
  2.   /**
  3.    * One can also add helper assert functions that might get used in tests
  4.    *
  5.    * This one test if the correct Exceptions is thrown (5.3 only)
  6.    */
  7.   protected function assertThrows(Closure $closure, $type, $error_message = NULL, $message) {
  8.     try {
  9.       $closure();
  10.     }
  11.     catch (Exception $e) {
  12.       if (!($e instanceof $type)) {
  13.         throw $e;
  14.       }
  15.       if (isset($error_message)) {
  16.         if ($e->getMessage() != $error_message) {
  17.           $this->fail($message, "SPS");
  18.           return;
  19.         }
  20.       }
  21.       $this->pass($message, "SPS");
  22.       return;
  23.     }
  24.     $this->fail($message, "SPS");
  25.   }
  26.  
  27.   /**
  28.    * One can also add helper assert functions that might get used in tests
  29.    *
  30.    * Test that an object is an instance of a class
  31.    *
  32.    * @param $class
  33.    * @param $object
  34.    * @param $message
  35.    */
  36.   protected function assertIsInstance($class, $object, $message) {
  37.     if ($object instanceof $class) {
  38.       $this->pass($message, "SPS");
  39.     }
  40.     else {
  41.       $this->fail($message, "SPS");
  42.     }
  43.   }
  44. }

Step 3 - Write Tests

OK, now we get to write tests! The structure here gets a little bit confusing, one can have as many test classes as one wants, and each test class can have as many test methods, and each test method can have many assertions.

In the SPS module we did a test class for each class provided by the SPS module, with a test method for each method provided by the class and then a test for each method. We found this to be an effective way to structure the test, but there is no required structure.

I also used one file for each test class, each of which extended the base class defined earlier. Each test class should define a getInfo method, to tell us about the test.

The getInfo method returns a array with keys of name, description, and group (I use the module name for this).

  1. class MODULETestnameUnitTest extends MODULEBaseUnitTest {
  2.   static function getInfo() {
  3.     return array(
  4.       'name' => 'MODULE Testname ',
  5.       'description' => 'Test the public interface to the Testname of the MODULE module',
  6.       'group' => 'MODULE',
  7.     );
  8.   }
  9.   ...
  10. }

Now for the test methods. Each method that starts with the word 'test' will be run as a test. Each test should include one or more asserts. One can look at the Drupal Unit Test methods to see all of the asserts. AssertTrue and AssertEqual will be the most used asserts.

  1. class MODULETestnameUnitTest extends MODULEBaseUnitTest {
  2.   ...
  3.   /**
  4.    * All test start with test in lowercase letters
  5.    *
  6.    * One can use any of the asserts in this space
  7.    */
  8.   public function testContruction() {
  9.     $value = $this->getCondition('baseball');
  10.     $this->assertIsInstance(
  11.       'MODULECondition',
  12.       $value,
  13.       'Description of what is being checked in this assertion'
  14.  
  15.     $expect = new ModuleCondition('baseball', 'other');
  16.     $this->assertEqual(
  17.       $value,
  18.       $expect,
  19.       'Description of what is being checked in this assertion'
  20.     );
  21.   }
  22.   ...
  23. }

Other methods (those that do not start with 'test') can be used in the test for doing tasks that might be needed by multiple test methods.

 

  1. class MODULETestnameUnitTest extends MODULEBaseUnitTest {
  2.   ...
  3.   /**
  4.    * Methods that do not start with test, are ignored as test, but can be used for function
  5.    * that might be reused on multiple test.
  6.    */
  7.   protected function getCondition($name) {
  8.  
  9.     return MODULE_get_condition($name, TRUE);
  10.   }
  11.  
  12. }

Running Tests

The last item is to run these tests; that can be done by using the /scripts/run-tests.sh script (this is off of the drupal root). One must have the simpletest module enable to run these tests.

To run only the tests in a specific group (remember I set it to the name of the module earlier) pass the group name as the first argument to the run-tests.sh script.

 php scripts<span class="sy0">/</span>run-tests.sh GROUPNAME
While one is developing one's tests, or if obe has to drill down on a failing test, one can use the verbose and class flags. This will give a more verbose report on only one class' tests.
 php scripts<span class="sy0">/</span>run-tests.sh --verbose --<span class="kw2">class</span> CLASSNAME

One can also use the Testing admin area for running the test (they are grouped by group name), but I find that the script is much more conducive if one is doing Test Driven Development.

Erik Summerfield

Director of Engineering