Temporary Repository for John Fiala's Simpletest docs

This is written by John Fiala and is stored temporarily until it is completed by John

Unit Testing – The Basics
The idea of unit testing is simple: To write a snippet of code that exercises a small amount of your code, hopefully the smallest amount possible, to make sure that what it does for a particular set of inputs matches your expectations. This is done by using assertions – built in comparison operators that notify you when the results you've received don't match your expectations. Although each test only covers a fraction of the functionality, having lots of automated tests which are easy and quick to run makes it possible to write new code and change existing code and still be sure that you're only changing the functionality that you want to change.
The ideal unit test would involve testing only one or two lines of the code, then have one assertion, and be done with it, but with the interrelations inside of the Drupal code, it's not quite that easy inside of a Drupal unit test. Another trait of an ideal unit test is one which has no side effects – once the test has been run, there is no left over data inside of the database from the test.
For example, if you had a function called average_numbers() that takes an array of numbers and returns the average, one unit test for it might be:
<?php
$items = array(2, 4);
$this->assertEqual(3, average_numbers($items));
?>
One method of doing unit tests in Drupal is to use the SimpleTest module, which depends upon the SimpleTest PHP unit test framework, and how to create and run those tests is summarized here.
SimpleTest Assertions and basics.
All simpletests are class-based – they're either based off of the UnitTestCase class, or the WebTestCase class, which extends UnitTestCase by adding an internal web browser and specialized assertions to make sure certain data in on the page. Drupal simpletests are further based off of the DrupalTestCase class, which inherits from WebTestCase and includes some handy drupal-related calls.
Don't worry if you're not familiar with object oriented programming. The only important things to remember is to keep the functions you add to the class inside of the curly brackets, and that if you're calling a function in the same class, you have to preface the function name with '$this->'. $this represents the object you're working in, and the arrow tells PHP that the function is part of the same object.

Note: For unknown reasons, some of the SimpleTest functionality doesn't work on Vista, or hasn't in the past. I suggest using a different operating system while testing.

Note2: Although SimpleTest tests attempt to clean up themselves after they are run, they sometimes do leave data behind that weren't deleted properly. Never run SimpleTest against a production site. (You will often find orphaned data if something causes a really bad error in the middle of a test run, it seems.)

How to start using Drupal simpletests:
The first step is of course to install the SimpleTest module – you can download it from the module page into your sites/all/modules directory. Once you've done that, you need to also download the simpletest php framework and copy it into the SimpleTest module – so the location of it in your project should be sites/all/modules/simpletest/simpletest. You want the very most recent version – not the 1.0, but what is currently the beta, which includes needed functionality. Once those are together, go in, activate the SimpleTest module through the Drupal administration, and then go to the 'SimpleTest unit test' page under Administer / Site Building (admin/build/simpletest). I suggest looking over the page, and even running the User tests – that will show if you have everything set up properly.

Once that's done, you'll need to create tests for the module itself. First, go into the directory of the module you want to test and create a subdirectory named tests, where you'll put your test files. Then, create a file in that directory with the extension '.test' – I suggest using '.test' at first, so if you were testing the RandomChoice module, you would create randomchoice.test. (The name, other than the extension, is for your own clarity – the testing module doesn't care. Choose something that will mean something to you.) Inside of the file, seed it with the following code:

<?php
/**
* This class is used to test the randomchoice module with simpletest.
*/
class RandomChoice extends DrupalTestCase {
function get_info() {
return array('name' => 'RandomChoice Test',
'desc' => t('Assure that RandomChoice works.'),
'group' => 'RandomChoice');
}

}
?>

The class name doesn't matter – again, use something that will mean something to you. The name and desc fields in the array are used inside of the SimpleTest module to describe the tests, and the group is used to group them together – if you have a lot of tests, you can split them up into several files, give them all the same group, and have the option of running them all at the same time.

The last step is to implement hook_simpletest() inside of your module. Simply paste the following code into your own module, making changes from 'randomchoice' to whatever your module is named:

/**
* Implementation of hook_simpletest().
*
*/
function randomchoice_simpletest() {
$dir = drupal_get_path('module', 'randomchoice') .'/tests';
$tests = file_scan_directory($dir, '\.test$');
return array_keys($tests);
}

Now that that's done, you've got the basic structure down and can start adding tests!

Tests: Your First Test

Adding a test is simple – just create a method inside of your new class (in that .test file you created above), and name it so it starts with 'test'. So, you could add the following test:

function testFrontPage() {
$this->drupalGet('http://localhost/drupal');
$this->assertTitle('Drupal');
}

This code does a very simple test – it goes to the indicated url, and then checks to make sure that the title of the page exactly matches that site. Add this to your test file, customize it to match the project's url and parameter, and then run it through the SimpleTest test page. And if it doesn't pass when you run it, have a look at the error message and decide if the problem is your test is bad – that's not the correct title – or if there's a problem with your site's configuration. If so, take a moment and fix it, and then run the test again to make sure that it's fixed.

This is possibly one of the simplest possible tests, but it consists of the main parts of a test – you start with setting up the situation you want to test, and then you use assertions to test the result. The tests don't necessarily have to use the internal web browser, either – if you've got a function in your module that returns data, you can call it in a test to make sure that what it returns is what you wanted.

Drupal Test Methods:
The Drupal SimpleTest module is prepared with a number of methods available that make unit testing with a drupal website much easier. drupalGet(), which we used just above, is just one of the simplest. Many of these work with the internal test browser that is part of the simpleTest browser - which allows you to go from page to page within your Drupal project without displaying them to the user.

Methods to browse with:
clickLink($label, $index = 0)
$label – the label of a link to follow. (Untranslated)
$index – If there's more than one link with the same text, this indicates which of them you want to click – if there's only one, then leave it at zero.
Starting with wherever the test browser of the framework is set to, click on the indicated link to go to another page.
drupalGet($url)
Goes to the location of $url in the test browser. Note that the url must be fully formed – this usually means you need to prepend http://localhost to it.
drupalPostRequest($path, $edit = array(), $submit, $edit_multi = array())
$path is the path of the page
$edit is an array of fields at $path – if a field is edit[name], then you would use 'name' as the array index.
$submit is the name of the submit button, untranslated.
$edit_multi was used before due to a bug in the underlying simpletest, but now can be ignored. It was used in the same way as $edit, but would contain arrays of selected or inputted data.
This test command goes to a particular page in the test browser, loads the fields of the form on the page with the values in $edit, and then submits the form. This is useful for creating new nodes, new users, or otherwise making sure that your forms are working.

Example:

<?php
$name = $this->randomName();
$mail = "$name@example.com";
$edit = array('name' => $name,
'mail' => $mail);
$this->drupalPostRequest('user/register', $edit, 'Create new account');
?>

Drupal Specialized Methods:
These methods do a little more than just go from page to page - they change something more complex in the Drupal environment.
drupalModuleEnable($name)
drupalModuleDisable($name)
Enables or disables a Drupal module by name. Useful for testing a module (or submodule) that may not be required. The module settings will be restored after the test method is done – as long as you're using 'return' instead of exit or die in your test.
drupalVariableSet($name, $value)
This works much like the Drupal api method variable_set, but once the test has ended the value is reset to the previous value.
drupalCreateUserRolePerm($permissions = NULL)
This function creates a user, and returns to you their user object with the additional field 'pass_raw', which holds the unhashed password. Both user name and password are randomly generated. It also will create a role with the given permissions (which is an array) which the user is then assigned to. If you pass no permissions, then the user will have 'access comments, access content, post comments, post comments without approval'. Once the test is done, the user (and role) will be deleted automatically.
drupalLoginUser($user = NULL)
This function logs a user into the site via the internal browser, using the $user object provided, which should either be from drupalCreateUserRolePerm or else a hand-rolled user-object with a pass_raw field containing the password in plaintext. If the $user is not provided, then the function calls the default drupalCreateUserRolePerm to get a user to log in.
drupalCreateRolePerm($permissions = NULL)
This function creates a new role, which has the permissions listed in the parameter array. There's not much reason to use this, but it returns either a FALSE or the role-id integer.

Example:
Let's say you want to test posting a blog entry, and making sure that the entry shows up on the front page. One way of testing that would be the following:

<?php
function testBlogFrontPage() {
$date = mktime();
$this->drupalLoginUser(); // Create and log in as a user.
$edits = array('title' => 'This is my blog post title '. $date, 'body' => 'Blog post goes here!');
$this->drupalPostRequest('http://localhost/drupal/node/add/blog', $edits, 'Submit'); //Submit blog post.
$this->drupalGet('http://localhost/drupal/'); // Return to front page

$this->assertLink('This is my blog post title '. $date, 'Looking for blog post on front page - %s');
}
?>

Once that's done, the user would be deleted from the system automatically.
Assertions:
Part of any automated test is an assertion, a way to test that what's going on in the test is what you're expecting to have happen. There's a wide number of assertions that you can call in SimpleTest. The first group of assertions are very general – is $a true, does $x equal $y, and so forth. If the assertion passes, then a green bar will be displayed at the end with what was compared. If it fails, a red bar is displayed instead.

assertTrue($x) Fail if $x is false
assertFalse($x) Fail if $x is true
assertNull($x) Fail if $x is set
assertNotNull($x) Fail if $x not set
assertIsA($x, $t) Fail if $x is not the class or type $t
assertNotA($x, $t) Fail if $x is of the class or type $t
assertEqual($x, $y) Fail if $x == $y is false
assertNotEqual($x, $y) Fail if $x == $y is true
assertWithinMargin($x, $y, $m) Fail if abs($x - $y) < $m is false
assertOutsideMargin($x, $y, $m) Fail if abs($x - $y) < $m is true
assertIdentical($x, $y) Fail if $x == $y is false or a type mismatch
Basically, Fails unless $x === $y
assertNotIdentical($x, $y) Fail if $x == $y is true and types match
Basically, Fails unless $x !== $y
assertReference($x, $y) Fail unless $x and $y are the same variable
assertClone($x, $y) Fail unless $x and $y are identical copies
assertPattern($p, $x) Fail unless the regex $p matches $x
assertNoPattern($p, $x) Fail if the regex $p matches $x
expectError($x) Swallows any upcoming matching error
assert($e) Fail on failed expectation object $e
fail() Fails the test.

Generally, these make sense, although the last two might require a few extra words. With most of these assertions, you just put them in after the code you're testing to see if the result is what you expected. With all of them there is an additional description parameter at the end that you can add to explain the error if the assertion fails – this is generally good practice so that someone using the tests has a better idea of what failed other than the fact that 5.4 isn't 5.2. You can include %s in that description to include the default error test, in case it's useful to the user to know what exactly the bad results are.
The expectError($x) assertion is a negative assertion – it says that in order for the code you are testing to be correct, it should raise an error (or exception). Sometimes the correct thing to do when a set of inputs are incorrect or unexpected is to raise an error for the calling code to handle – and in that case, expectError() lets you test for that. If an error is raised matching $x (or any error, if $x is ''), then the error is swallowed and the test continues – otherwise, if a matching error doesn't occur, then the test is marked as failing.
The assert($e) assertion is more complicated – it uses the expectation objects that are more often used in mock objects. Generally, this is only something you should look into when you're having trouble getting the standard assertions to do what you need.
The fail() assertion is just that – calling it fails the test. This is sometimes necessary.

Web Assertions:
In additional to the standard assertions above, there are additional assertions that are built into the internal web browser of the WebTestCase and DrupalTestCase objects, that test for the existence (or non-existence) of data in the current page.

assertTitle($title) Pass if title is an exact match
assertText($text) Pass if matches visible and "alt" text
assertNoText($text) Pass if doesn't match visible and "alt" text
assertPattern($pattern) A Perl pattern match against the page content
assertNoPattern($pattern) A Perl pattern match to not find content
assertLink($label) Pass if a link with this text is present
assertNoLink($label) Pass if no link with this text is present
assertLinkById($id) Pass if a link with this id attribute is present
assertNoLinkById($id) Pass if no link with this id attribute is present
assertField($name, $value) Pass if an input tag with this name has this value
assertFieldById($id, $value) Pass if an input tag with this id has this value
assertResponse($codes) Pass if HTTP response matches this list
assertMime($types) Pass if MIME type is in this list
assertAuthentication($protocol) Pass if the current challenge is this protocol
assertNoAuthentication() Pass if there is no current challenge
assertRealm($name) Pass if the current challenge realm matches
assertHeader($header, $content) Pass if a header was fetched matching this value
assertNoHeader($header) Pass if a header was not fetched
assertCookie($name, $value) Pass if there is currently a matching cookie
assertNoCookie($name) Pass if there is currently no cookie of this name

These all support the same additional description term as with the first group of assertions, and are very useful for figuring out if the browser is in the state you need it to be. So, if you need to create a node via node/add/type, and then want to test if the node was created properly, you could use assertText('The node was created') to test that you were successful.

Setup and Teardown: Simplifying and Debugging Your Tests
Sometimes a set of tests are very similar. You log in a user, you do something with his information or go through some pages, you check the results, and then you're done. Sometimes you've got data in a custom table that needs to be removed, also, and each test starts and ends with the same code.
Luckily, there's a way to remove that code to an automatic start and end function: they're called setUp() and tearDown(). The setUp() function is called before each test in a given class, and is good for putting common setup code, such as creating a user and logging him in. The tearDown() function is called after each test in a given class, and is good for putting cleanup code. Each of them gets called once per test.

<?php
function setUp() {
parent::setUp()
$this->drupalLoginUser();
}

function testThis() {
// Include test that assumes that a user is logged in.
}

function testThat() {
// Include a different test that assumes that a user is logged in.
}

function tearDown() {
// Clean up anything you added to the database that is no longer needed.
parent::tearDown()
}
?>

If you've got some tests that need a common setup and tear down, and others that don't, do some organization – create a new test file in the tests directory, name it something.test, copy over the class declaration, the get_info() function, and the tests that need setUp/tearDown. Don't forget to change the name and desc fields of the array that get_info returns – but keep the group the same. That way, you can still run all of these tests with the same checkbox.