Simpletest Tutorial
This tutorial will take you through the basics of testing in Drupal and by the end you should be able to write your first test! For this example we will create a dummy module called "mymodule". Mymodule does nothing more than provide a content type called "mymodule", which is exactly the same as any basic Drupal node type (like 'page'). The tutorial will then explain how to test that the custom content type provided to ensure it functions properly.
How Drupal's Simpletest works
Most of Drupal is web-oriented functionality, so it's important to have a way to exercise these functions. Simpletest creates a complete Drupal installation and a virtual web browser and then uses the virtual web browser to walk the Drupal install through a series of tests, just like you would do if you were doing it by hand. It's terribly important to realize that each test runs in a completely new Drupal instance, which is created from scratch for the test. In other words, none of your configuration and none of your users exists! None of your modules are enabled beyond the default Drupal core modules. If your test sequence requires a privileged user, you'll have to create one (just as you would if you were setting up a manual testing environment from scratch). If modules have to be enabled, you have to enable them. If something has to be configured, you'll have to use Simpletest to do it, because none of the configuration on your current site is in the magically created Drupal instance that we're testing. None of the files in your files directory are there, none of the optional modules are installed, none of the users are created. We have magic commands to do all this within the Simpletest world, and we'll get to that in a little bit.
What "mymodule" is and what it does
Mymodule provides an additional node type (like 'page' or 'story'). It has a title and a body. That's it. It gives us a chance to demonstrate testing of content creation, for example. To make implement the node, we have to provide the node type to Drupal with hook_node_info() and provide a form for the node type with hook_form(). We implement permissions for the module (so that you need "create mymodule" permissions to create one, or "edit own mymodule" permissions to edit one.) And of course we need a mymodule.info.
Note that our module has a bug in it: The permissions handling is not done correctly. So even though there's a permission string for 'edit own mymodule', it's not handled correctly by mymodule_access(), so when our properly privileged user tries to edit a node, it can't. Of course, the manual user tester was probably testing with user 1, so never saw this failure case. We'll get to this later.
You can view the code or install the module right from this page. It really helps to try out the code, change it a bit, experiment with dummy code like this before trying to do something serious.
- Mymodule for Drupal 6
- Mymodule for Drupal 7 (updated 2009-09-05)
Figuring out what we need to test
If you install mymodule from this page, you can manually go through the steps and see what you think needs to be tested.
Visit node/add/mymodule where you should see the following. (screenshot has been stripped down for simplicity)
Look at the interface and identify the things that need to be tested in our node type.
Take a tour and make sure you are familiar with how the interface works and that it functions properly in a basic case where you are manipulating it instead of simpletest. If you don't understand what it's doing or why, you can't write a workable test. In this case fill in the title and body fields and click the save button. You should see something like the following.
Building a test for mymodule
Now it's time to create our tests, which we'll do in the mymodule.test file (which you have in the complete module download)
There are three basic steps:
- Creating the structure (just creating a class that inherits from DrupalWebTestCase)
- Initializing the test case with whatever user creation or configuration needs to be done
- Creating actual tests within the test case
- And, of course, trying desperately to figure out why our test doesn't work the way we expect, and debugging the test (and perhaps the module)
To start, we just need a bit of boilerplate extending DrupalWebTestCase.
<?php
class MymoduleTestCase extends DrupalWebTestCase {
}
?>To make the test available to the Simpletest testing interface, we implement getInfo(). This just provides the user interface information that will show up on the simpletest page.
<?php
class MymoduleTestCase extends DrupalWebTestCase {
public static function getInfo() {
return array(
'name' => 'mymodule',
'description' => 'Ensure that the mymodule content type provided functions properly.',
'group' => 'Demonstration tests',
);
}
}
?>Next comes the terribly important setUp(). Here is where we must do anything that needs to be done to make this Drupal instance work the way we want to. We have to think: "What did I have to do to get from a stock Drupal install to where I can run this test?". In our case, we know that we had to:
- Enable the mymodule module
- Create a user with privileges to create a mymodule node
- Log in the user
This work is done by the setUp() method:
<?php
public function setUp() {
parent::setUp('mymodule'); // Enable any modules required for the test
// Create and log in our user
$privileged_user = $this->drupalCreateUser(array('create mymodule'));
$this->drupalLogin($privileged_user);
}
?>Create specific test: Creating a node
Now we need to create specific tests to exercise the module. We just create member functions of our test class, each of which exercises a particular test.
Our first test will be to create a new mymodule node by using the form at node/add/mymodule:
<?php
// Create a mymodule node using the node form
public function testMymoduleCreate() {
$langcode = FIELD_LANGUAGE_NONE;
$body_key = "body[$langcode][0][value]";
// Create node to edit.
$edit = array();
$edit['title'] = $this->randomName(8);
$edit[$body_key] = $this->randomName(16);
$this->drupalPost('node/add/mymodule', $edit, t('Save'));
$this->assertText(t('mymodule page @title has been created.', array('@title' => $edit['title'])));
// For debugging we can output the page so it can be opened with a browser
// Remove this line when the test has been debugged
$this->outputScreenContents('After page creation', 'testMymoduleCreate');
}
?>Running the Simpletest web interface
Next we need to run the test. Here we'll use the web interface to run the test.
Navigate to Admin->Site Building->Testing admin/build/testing (in Drupal 6) or Admin->Development->Testing (admin/config/development/testing in Drupal 7, select the test you just created, and press the Run tests button.
Once the test has run you should see the results, which for this test will pass.
A demonstration failing test
It really doesn't teach us much to just have a test that succeeds. Let's look at one that fails.
We'll work within the same class and this time try to test editing a node. Our module has a flaw in it - it doesn't handle the 'edit own mymodule' permission string correctly, so a user with only that permission will not be able to edit it. In this test we'll create a node and then try to edit it.
Run the test and see the result.
<?php
// Create a mymodule node and then see if our user can edit it
public function testMymoduleEdit() {
$settings = array(
'type' => 'mymodule',
'title' => $this->randomName(32),
'body' => array(FIELD_LANGUAGE_NONE => array(array($this->randomName(64)))),
);
$node = $this->drupalCreateNode($settings);
// For debugging, we might output the node structure with $this->pass()
// $this->pass('Node created: ' . var_export($node,TRUE));
$edit_path = "node/{$node->nid}/edit";
$this->drupalGet($edit_path);
$this->assertFieldById('edit-body-zxx-0-summary','', 'Looking for edit-body-zxx-0-summary field as indication that we got to the edit page');
// For debugging we can output the page so it can be opened with a browser
// Remove this line when the test has been debugged
$this->outputScreenContents("After drupalGet($edit_path) in testMymoduleEdit", 'testMymoduleEdit');
}
?>Unit Testing
Simpletest also provides a DrupalUnitTestCase as an alternative to the DrupalWebTestCase.
The database tables and files directory are not created for unit tests. This makes them much faster to initialise than functional tests but means that they cannot access the database or the files directory. Calling any Drupal function that needs the database will throw exceptions. These include routine functions like watchdog(), drupal_function_exists(), module_implements(), module_invoke_all() etc.
Debugging Simpletests
Since we can't see what's going on in that magical browser, it can be pretty hard to figure out what's going on with our test (or perhaps with the code we're testing). It's like trying to tell somebody over the phone how to run a piece of software. You tell them, but they misunderstand, and they don't report back to you correctly what's wrong, so you can't even guess.
So we need debugging output (which should generally be removed when everything works).
A first key technique is to use the $this->pass() pseudo-test to send information to the Simpletest web interface. $this->pass() really is just an assertion that always succeeds, so it's a way to pass some information (like a dump of a node or user, for example) back to the web interface.
Second, and critical, is the dumping of the screen to a file where it can be viewed in a browser. This can settle all kinds of problems. In the example mymodule.test is a utility function that does this for us, and it's called by both tests:
<?php
private function outputScreenContents($description, $basename) {
// This is a hack to get a directory that won't be cleaned up by simpletest
$file_dir = file_directory_path().'/../simpletest_output_pages';
if (!is_dir($file_dir)) {
mkdir($file_dir, 0777, TRUE);
}
$output_path = "$file_dir/$basename." . $this->randomName(10) . '.html';
$rv = file_put_contents($output_path, $this->drupalGetContent());
$this->pass("$description: Contents of result page are ".l('here',$output_path));
}
}
?>Where to go from here
- Several of the API functions and assertions are documented in this section.
- Reading through drupal_web_test_case.php is instructive
| Attachment | Size |
|---|---|
| mymodule.drupal6.tgz | 1.75 KB |
| simpletest_mymodule_demo.d7.20090905.tgz | 1.96 KB |

Remember to alter simpletest.info when writing a core test
Not sure where to put this.
In trying to write a core test I failed to get my test into the 'System' tests.
In the end it came down to add the test to simpletest.info
<?phpfiles[] = tests/update.test
?>
and to rebuild the registry. How? I reinstalled the simpletest module though devel.
Tip: make sure to enable all modules needed in setUp()
In my case I found that Setup() does not handle module dependencies and I needed to enable all modules required by my module for testing. To help check that I have enabled the correct modules I simply created a user and went to the modules page to see that they where available and enabled.
<?php
private function displayModulesSettings() {
// Create and log in our user
$privileged_user = $this -> drupalCreateUser(array('administer site configuration'));
$this -> drupalLogin($privileged_user);
// go to and show the modules page
$this->drupalGet(url("admin/build/modules", array('absolute' => TRUE)));
$this->assertResponse('200', 'expected 200');
$this->outputScreenContents('See modules page', 'See modules page');
// logout
$this -> drupalLogout($privileged_user);
}
?>
The function
outputScreenContentscan be found in the Debugging Simpletests section above.This also can be helpful when creating a user with specific permissions and you get an error that the permission was not valid. Just use the same code above and go to the
admin/user/permissionspage with'administer permissions'to confirm that the permission is available.I hope this helps someone, it sure helped me.
Your new test cases not showing up?
If you've added your test cases in a new file and now want to execute them but they don't show up under SimpleTest.
This is probably because there is a cache that hasn't been invalidated yet.
This code in simpletest.module line 358.
<?phpif ($cache = cache_get('simpletest', 'cache')) {
$groups = $cache->data;
}
?>
To resolve this just go in to the database and remove the cached item.