Demystifying Drupal unit testing with Simpletest
Think unit testing is ridiculous unnecessary overhead? So did I until I actually tried them, and while writing tests isn't glamorous the benefits are unbelievable. Even better, the Simpletest module makes writing unit tests for Drupal a snap.
Benefits
Ever introduce a bug that you didn't find out about until days (weeks, months) after your code went live? No? How bout this then; ever spend a few hours grovelling around on your site testing features for a new module you're writing? Ever get the nagging feeling that even after hours of testing you missed something?
In all of these instances unit testing can (and does) help. With a solid set of tests backing up your site you can do all of the following in seconds:
-
Test your module's entire feature set.
Confirm the latest module(s) installed on your site aren't breaking any pre-existing code.
Test patches to ensure they don't break anything.
A real-world example
Seriously, after seeing what unit testing can bring to the table first hand I believe it's difficult to overstate the potential for awesomeness. Here's an example: I recently did a partial re-write of the Force Password Change module for work. The module was missing tests and my boss is a stickler about such things so I ended up tasked with writing a test suite for the following:
-
Log in an unprivileged user and confirm the FPC admin page returns a 403 error.
Test the form submission, including confirmation that bogus input gets the correct error message
Configure the "authenticated users" role to force reset, then log in a user and confirm they're forced to change their password.
Confirm the password change works as directed, also confirm admin accounts can still edit the user account without having to change the account password.
Force a single user account to change their password & confirm
Log user out and confirm they aren't forced to change their password again when they log back in.
Now, FPC is a pretty simple module but even so doing a full test of the feature set is kind of complicated and potentially error-prone.
I haven't actually timed how long it takes to walk through all of those steps (creating at least three user accounts, an admin role, lots of config and form submits) but I'm going to estimate it'd take at least 15-30 minutes to do it properly by hand. With a test suite I, or anyone else who feels like patching the module, can confirm all of this is working (or if not what broke) in seconds.
Sounds great! How do I get started?
First thing you'll need is a working dev installation of Drupal and a copy of the Simpletest module. I say dev installation because Simpletest requires a core patch to work, which (in my opinion) rules it out for deployment on production sites. Besides, you don't develop on your live site, right?
Installing Simpletest
Installation is fairly straight forward. To get the module either download the latest copy from the project page, or, if you're already set up with drush just cd to your sites/all/modules folder and run
drush dl simpletest
While you're at it the Coder module makes an excellent companion to Simpletest, especially if you plan on reviewing or submitting patches.
Once you have the module downloaded you'll need to copy the D6-core-simpletest.patch file to the root directory of your Drupal install and then apply it like so:
patch -p0 < D6-core-simpletest.patch
Once you've applied the patch you should be able to enable the Simpletest module via admin/build/modules. Once you've completed the installation you should see a new menu item testing in your admin/build menu.
Simpletest Interface

As you can see from the screenshot running tests couldn't be simpler. Just select the test(s) you want to run then click on "run tests".
A word on the Clean environment button: since Simpletest uses table prefixes to create a Drupal site (including database tables) on the fly, if a test bombs out (usually due to a php error in the test suite) it can leave a real mess in your database. If this happens, clicking on Clean environment forces Simpletest to go through the database and remove any leftover tables it's created during previous failed test runs.
Note this is only necessary if a suite of tests exits in a wildly abnormal fashion. Normal failing tests don't require this kind of cleanup.
Once your tests have completed Simpletest provides a detailed report of which tests where run and if they passed. Failing tests are highlighted red.

Ok, cool, so how do I add unit tests to my modules?
Adding unit tests to your module is as simple as writing a .test file. This file serves as a container for your unit test code and keeps tests separate from your module code. Test files are namespaced the same as .module or .info files, which is to say they should be named the same as your module (example: force_password_change.test).
Unlike the bulk of Drupal's codebase, unit tests are object oriented which may take some getting used to. We start by declaring a class for our module's tests that will extend Simpletest's DrupalWebTestCase class:
class MyModuleTestCase extends DrupalWebTestCase {
// your code goes here.
}
For those feeling curious, DrupalWebTestCase is defined in drupal_web_test_case.php within the Simpletest module folder. Knowing where this is comes in handy since the class is well commented and basically documents itself.
We need to tell Simpletest about our tests so the first thing we do is implement the getInfo() method:
public static function getInfo() {
return array(
'name' => 'Force password reset test',
'description' => 'Tests forced password change for single user,
role and all new users.',
'group' => 'Force Password Change',
);
}
Now we add a setup method to the class. This method tells Simpletest what steps to take before any tests are run. Since Simpletest bootstraps a largely unconfigured copy of Drupal to run tests against, at minimum your setup function should tell Simpletest to install your module.
public function setUp() {
parent::setUp('force_password_change');
}
With the preliminaries out of the way we can start adding tests. The example below creates a privileged user and checks to make sure they have access to the force password change admin form.
public function testForm() { $this->admin_user = $this->drupalCreateUser(array('administer force
password change'));
$this->drupalLogin($this->admin_user);
$edit = array();
$this->drupalGet('admin/user/force_password_change');
$this->assertText('Force users in the following roles to change their
password', 'Found admin form');
...
Test code so far:
class MyModuleTestCase extends DrupalWebTestCase {
public static function getInfo() {
return array(
'name' => 'Force password reset test',
'description' => 'Tests forced password change for single user,
role and all new users.',
'group' => 'Force Password Change',
);
}
public function setUp() {
parent::setUp('force_password_change');
}
public function testForm() { $this->admin_user = $this->drupalCreateUser(array('administer force
password change'));
$this->drupalLogin($this->admin_user);
$edit = array();
$this->drupalGet('admin/user/force_password_change');
$this->assertText('Force users in the following roles to change
their password', 'Found admin form');
...
}
There's quite a bit going on here in a small space. We're creating a new user, a role, assigning privileges to the role, assigning the role to the user, logging the user in and finally confirming they can access the admin form. Let's take a closer look at some of this.
Navigation
Writing unit tests for Drupal is all about mimicking user interaction with a site. Simpletest comes with methods that emulate visiting a specific page, posting a form or clicking on a specific link.
View a page: $this->drupalGet($url)
When called in a test this method attempts to retrieve the specified URL as a prelude to any assertions you want to test against it.
$this->drupalGet('user');
Even better, Simpletest throws in two freebie tests (return status and valid HTML) with every GET or POST.

Note: unless you have created and logged in a privileged user before calling this method Simpletest will attempt to retrieve the given URL as an anonymous user.
Submit a form: $this->drupalPost($url, $edit, $button)
This method is a quick way to interact with forms. The menu path of the form to submit is set in $url. Field values are defined in the $edit array(). $button tells drupalPost() which form button to "click" on when submitting the form.
//log in as fpc_test_user_01
$edit = array(
'name' => 'fpc_test_user_01',
'pass' => 'lKo01skd8!',
);
$this->drupalPost('user', $edit, 'Log in');
Click a link: $this->clickLink($label, $index = 0)
This simulates a user clicking on a link. Defaults to clicking on the first link that has text matching $label unless $index is set.
//log out
$this->clickLink('Log out');
Simpletest comes with a ton of useful API methods so definitely check out the API docs for more information.
Working with users
In addition to navigating your site with tests you also need to be able to view pages as anonymous or authenticated users. To do that you need to be able to create user accounts, assign permissions and log users in and out.
The easy way: $this->drupalCreateUser($permissions = array())
Simpletest provides a convenient method for creating users on the fly: drupalCreateUser().
//create a privileged user and log them in
$this->admin_user = $this->drupalCreateUser(array(
'administer force password change',
'administer users',
'access user profiles',
'access administration pages'));
$this-> drupalLogin($this->admin_user);
drupalCreateUser() creates a new user and assigns permissions from the permissions array.
The hard way: create a user with drupalPost()
If you need more granular control (like control of a user's name or password for example) you can also create user accounts manually using drupalPost().
$edit = array(
'name' => 'fpc_test_user_01',
'mail' => 'fpc_test_user_01@example.com',
'pass[pass1]' => 'lKo01skd8!',
'pass[pass2]' => 'lKo01skd8!',
);
$this->drupalPost('admin/user/user/create', $edit, 'Create new account');
Note this only works if you've already created (and logged in) a user account that has the 'administer users' privilege. Otherwise drupalPost() gets a 403 error.
Log in, log out
Logging users in is easy. If you've already created a user you can use drupalLogin() to log them in.
$this-> drupalLogin($this->admin_user);
You can also use drupalPost() to log a user in manually.
$edit = array(
'name' => 'fpc_test_user_01',
'pass' => 'lKo01skd8!',
);
$this->drupalPost('user', $edit, 'Log in');
Logging out is even easier. For users created by drupalCreateUser():
//log out admin_user $this->drupalLogout($this->admin_user);
Or manually log out:
//emulates clicking the log out link
$this->clickLink('Log out');
Assertions, where the magic happens
represent events and/or values you are actively testing for when a user visits a certain page or a form is submitted.
One of the most commonly used is assertText():
$this->drupalGet('admin/user/force_password_change');
$this->assertText('Force users in the following roles
to change their password', 'Found admin form');
The example code attempts to retrieve the Force Password Change admin page with drupalGet(), then uses assertText() to look for the string "Force users in the following roles to change their password". If the string is found the test passes, otherwise it fails. Either way this test is labeled "Found admin form" in the test results.

Unfortunately it's outside of the scope of this article to cover all of the available assertion types. For a detailed list check out the Simpletest assertions documentation
Wrap up
While writing tests definitely represents a time investment, you will quickly find it pays dividends when you can test a modules entire feature set in a matter of seconds.
Throughout this article I've used portions of the test suite I wrote for Force Password Change as example code. Unfortunately at time of writing these (and the rest of my patch) haven't been incorporated into the module and I haven't heard anything from the maintainer, so until something changes here's a link to my version of the module: Force Password Change (unsupported rogue version)
For those interested in additional information here are some links:
- freeman's blog
- Login or register to post comments

Are you applying the patch to a code base you eventually push to a production environment? Or did you patch a cloned/silo'd/etc version of your site in order to run Simpletest?
I want to explore using Simpletest, but am unsure if I should patch my dev environment which pulls from same repository as our staging and production environments. (I, of course, would not run Simpletest on our production environment).
Just curious...
- Login or register to post comments
Submitted by thund3rbox on Thu, 04/15/2010 - 20:11.I've only got my local dev environment (laptop) patched to work with simpletest. Since core files are updated elsewhere (staging) I've got no reason to do an SVN commit from Drupal webroot. My local copy of simpletest is checked out from CVS and hasn't been added to our SVN repo so even commits from sites/* is safe.
- Login or register to post comments
Submitted by freeman on Mon, 04/19/2010 - 14:41.