Before we get into writing our own tests we need to enable the SimpleTest module, and while we're at it we may as well talk about the various configuration options it has. And then why not run a few of the tests that come with Drupal core as a way to learn how to discover and run all the tests, a group of tests, or even an individual test case.
The SimpleTest module comes with Drupal 7 core, but is not enabled by default. So we can start by enabling the module. On the administration page for enabling modules the module is listed as "Testing" instead of SimpleTest which can be a bit confusing. The the UI you'll almost always see the name "Testing", but in the code, documentation, and at the command line it's generally referred to as SimpleTest.
Once the module is enabled there are just a few new permissions and configuration options to cover. The new 'run tests' permission, and a couple of settings related to enabling verbose output for debugging of tests. Make sure you check them all out.
Tests can be run in a variety of different ways, either from your browser using the Testing module's UI, or at the command line with drush. We'll cover both of them in this lesson. And in doing so we'll look at how to run a group of tests, or an individual test case, and the output that's generated by running tests.
I've also got a module (which you can download from this page) that contains a couple of failing tests. This way we can run a test case that fails, and see what the output looks like from a failure. SimpleTest outputs some helpful debugging information, and with the verbose option enabled will even save the generated HTML of each page the SimpleTest browser saw so that we can review it after the test run is complete.
If you're not familiar with drush checkout our series on using drush.
By the end of this lesson you should be able to enable and configure the simple test module and run some tests.
Example drush commands
Run all tests in the "Block" group:
drush -l http://localhost/demos/simpletest-7x/docroot test-run Block
Run just the "BlockHashTestCase" test case:
drush -l http://localhost/demos/simpletest-7x/docroot test-run BlockHashTestCase
Note: as of Drush 7 the drush test-run
command is no longer part available. See https://github.com/drush-ops/drush/issues/599.
You can either, use Drush 6, or use the scripts/run-tests.sh
scrip that comes with Drupal core. See the documentation for the run-tests.sh script for more information.
In this lesson we'll write a hello world test that navigates to the front page of a standard Drupal installation and verifies the existence of the text, "No front page content has been created yet.". This will allow us to walk through creating a .test file, adding it to our modules .info file, as well as cover the basics of extending DrupalWebTestCase
to write our own test case. For this lesson we'll use the helloworld sample module as a starting point and expand it to add a very basic test. The goal being to gain a better understanding of how the SimpleTest module discovers, and runs our tests. Then in future lessons we can expand on our test case to add more robust features.
All SimpleTest tests are an extension of either the DrupalWebTestCase
(we'll focus on this one), or the DrupalUnitTestCase
base classes. There are two pieces to the discover process. Writing a new class that extends one of the base classes, and then declaring the file that contains this new class in your module's .info file so that the file and it's code are indexed and inserted into the registry.
Create a new file inside the helloworld module's directory: tests/helloworld.test. The convention is to name files that contain test classes with a .test extension. Similar to how module file, despite being purely PHP, are named with a .module extension.
Then we'll add a basic skeleton test class like so:
class HelloworldTests extends DrupalWebTestCase {
/**
* Metadata about our test case.
*/
public static function getInfo() {
return array(
'name' => 'Hello World',
'description' => 'Tests for the Hello World module.',
'group' => 'Hello World Group',
);
}
}
Our class includes a getInfo()
method, it's required, and it returns a simple associative array with meta-data about our test. This information is used by the SimpleTest UI and other test runners to provide human readable information about our tests and to do things like group like tests together. When you view the lists of tests in the SimpleTest UI, you're seeing the 'name', and 'description' provided by your test classes' getInfo() method.
All tests should have a setUp() method as well. This method is called by the test runner after Drupal has been installed, but before our tests are run. It gives us the chance to perform any additional configuration on the environment within which the tests are running. In our case, we need to enable the helloworld module since it's not part of the default install profile. We can do that by delegating to the parent classes' setUp() method and passing an array of modules we want enabled. That might look something like this:
/**
* Perform any setup tasks for our test case.
*/
public function setUp() {
parent::setUp(array('helloworld'));
}
Finally, we need to provide at least one method whose name starts with "test". This method, and all others whose name starts with test will be called in succession by the test runner and are expected to contain the assertions that make up our individual test.
public function testHelloWorld() {
$this->drupalGet('helloworld');
$this->assertText('Hello World. Welcome to Drupal.', 'The page content is present.');
}
This test will navigate to the url /helloworld, and then verify that the text "Hello World. Welcome to Drupal.", is displayed on the page.
In order for our test to show up in the SimpleTest UI we need to add it to the registry. We can do so by modifying the helloworld.info file and adding the line files[] = tests/helloworld.test
to the file. Then clear the cache and Drupal should discover your new test case and allow you to run the tests either through the SimpleTest UI or with drush.
Additional resources
DrupalWebTestCase documentation
Written tutorial based on this video
Assertions are like the answer sheet a teacher uses when grading a multiple choice test. When we write tests our primary objective is to check, or assert, that the state of some system, or the value of some variable, in the specified context matches our expectations. Like saying, "I expect that when I break this cookie in half I'll see chocolate pieces.". If you break the cookie and they are not there you might then decide that this particular cookie does not pass the chocolate chip cookie test. And flag it for review.
There are a lot of different types of assertions that we can make when using SimpleTest and this lesson attempts to demystify what each of the different types of assertions does. There is some really good documentation about the various types of assertions and some examples of using them in the Assertions documentation page on Drupal.org.
Not all of the assertions in SimpleTest are listed there though. For that, your best bet is to look at the API documentation for DrupalWebTestCase, and use the filter field at the top of the table to limit the list to only methods that start with the keyword "assert". This will give you a complete, and up-to-date list of all the assertions.
Basic assertion types reference:
Checking the value of something
- assertTrue
- assertFalse
- assertNull
- assertNotNull
- assertEqual
- assertNotEqual
- assertIdentical
- assertNotIdentical
Checking for the presence of something
- assertPattern
- assertNoPattern
- assertRaw
- assertNoRaw
- assertText
- assertNoText
- assertTitle
- assertNoTitle
- assertUniqueText
- assertNoUniqueText
- assertLink
- assertNoLink
- assertResponse
Checking form elements
- assertFieldById
- assertNoFieldById
- assertFieldByName
- assertNoFieldByName
- assertFieldChecked
- assertNoFieldChecked
- assertOptionSelected
- assertNoOptionSelected
Additional resources
If we're going to do functional testing of the various pages in our application we'll need to be able to navigate to the pages in question. The DrupalWebTestCase's built in browser provides us with two different ways to navigate our site. In this lesson we'll explore using the DrupalWebTestCase'::drupalGet()
, and DrupalWebTestCase::clickLink()
functions in order to navigate throughout our application. The former is useful when we know the exact URL of the page we want to navigate to and the latter is useful when we don't necessarily know the URL but we know which link to follow to get there.
I like to think of DrupalWebTestCase::drupalGet()
as typing an address into the URL bar in my browser. I know the page I want to navigate to so take me right to it. DrupalWebTestCase::drupalGet()
can be used inside a test case like so:
public function testHelloWorld() {
$this->drupalGet('helloworld');
}
This points the SimpleTest internal browser to the helloworld
page on our site, and loads the HTML content of that page into the buffer so we can perform assertions on its content.
DrupalWebTestCase::clickLink()
on the other hand is more like looking at the content of the page, reading through the links in the navigation menu and then deciding that you would like to click on the one labeled "Hello World". You don't know what address it's going to take you to, but that's okay because what matters is that you end up on the page. DrupalWebTestCase::clickLink()
operates by scanning the SimpleTest browsers buffer of the current page's HTML content for any link whose label matches the value provided.
public function testHelloWorld() {
$this->clickLink('Hello World');
}
This would have the effect of clicking on the following link:
<a href="helloworld">Hello World</a>
Or really, just extracting the value of the href attribute from that a tag and feeding it to DrupalWebTestCase::drupalGet()
.
I find the best reference for these helper methods is located in the Drupal.org handbook. https://www.drupal.org/node/265762
Additional resources
Much of the functionality that web based applications provide is in the form of an HTML form. Enter some data into the various fields, press the submit button, and see what happens. The DrupalWebTestCase provides a handful of methods to aid in interacting with forms. In this lesson we'll take a look at filling out various types of form fields, submitting forms, and validating the result.
We'll start by navigating to a page that contains a form and simply verifying that the appropriate fields are found on the page.
$this->drupalGet('helloworld/form');
$this->assertFieldByXpath("//form[@id='helloworld-cake-form']//input[@name='name']", '', 'The name field is present.');
$this->assertFieldByName('choice', 'cake', 'The choice field is present.');
This code checks for the input name="name"
field, and the select name="choice"
field, and ensures they are present on the page at hellworld/form.
Then we'll use the DrupalWebTestCase::drupalPost()
method to fill in, and submit, a form. Here's an example:
$data = array(
'name' => '',
'choice' => 'chicken',
);
$this->drupalPost('helloworld/form', $data, 'Submit');
$this->assertText('Your name field is required.', 'Name field is required.');
This code fills in the form on the hellworld/form page, and then clicks the submit button using the DrupalWebTestCase::drupalPost()
method. The first argument is the URL at which the form lives, the location to which the HTTP POST request will be sent. The second argument is an associative array of values for the form fields. The keys are the names of the input elements, and the values are the information we would like to enter into those fields.
In this lesson we also make use of the $this->assertFieldByXPath()
method, which allows us to select a form element on the page using an XPath selector. In our case, we need to do this because there are actually two input element on the page with their name attribute set to "name", and we need to make sure we're testing the correct one. If you're not familiar with XPath it's worth taking some time to review the syntax since chances are you're going to end up using it at some point when working with SimpleTest.
For more information about these helper methods checkout the API Functions handbook page.
Additional resources
Typically our application is going to require testing of functionality that is restricted to only users with a certain set of permissions. Like creating a new node for example. In order for the DrupalWebTestCase browser to test these pages we need to first log in to our site. In order to log in we need to have a username and password that we can use to login. In this lesson we'll look at using DrupalWebTestCase::drupalCreateUser()
and DrupalWebTestCase::drupalLogin()
in order to gain authenticated access to our application and then access and submit a restricted form.
Here's an example of creating a user with the 'administer site configuration` permissions:
$new_user = $this->drupalCreateUser(array('administer site configuration'));
And then logging in as that user:
$new_user = $this->drupalLogin($new_user);
After that the SimpleTest browser will be logged in as that user and all requests that it makes will be authenticated. If you want to return to an anonymous user session you can use the DrupalWebTestCase::drupalLogout()
method.
For more information about these helper methods check out the API Functions documentation in the Drupal.org handbook.
Additional resources
SimpleTest helper method documentation
There's another set of really useful helper methods that we haven't looked at yet which are geared towards creating dummy content for testing. Including simple tasks like generating random strings, or random names of a specified length, and more complex things like creating nodes, or even new content types. In this lesson we'll take a lot at the various helper methods in DrupalWebTestCase that assist in the creation of dummy content by creating various pieces of random content and then testing functionality based on that content.
In order to demonstrate these tools lets look at writing some tests for the core statistics module which amongst other things tracks page views for content. In order to perform these tests we're going to need a piece of content that we can view, and then view again to ensure that the statistics are properly updating.
Creating random strings can be done with DrupalWebTestCase::randomName()
and DrupalWebTestCase::randomString()
. Both methods take an argument that allows you to set the number of characters that should be returned, or you can just leave out to get the default. The difference between the two is that DrupalWebTestCase::randomName()
will only use alphanumeric characters and the returned string will always start with a letter. This is useful for things like generating random usernames or email addresses, or other things that don't allow for special characters.
The DrupalWebTestCase::drupalCreateNode()
helper can be used to create nodes. While you could navigate to and fill out the form using SimpleTest this is much quicker and requires far less code. Here's an example of creating a node:
$title = $this->randomName();
$settings = array(
'type' => 'page',
'promote' => 1,
'title' => $title,
'body' => array(LANGUAGE_NONE => array(
array(
'value' => 'Copy goes here',
'format' => filter_default_format(),
),
)),
);
$node = $this->drupalCreateNode($settings);
This will create a new page node with a random title and the string "Copy goes here" in the body field. The array of values passed to DrupalWebTestCase::drupalCreateNode()
should mirror the structure of a node object. If you're unsure what that structure is one easy way to figure it out is to use node_load() to retrieve an existing node and inspect the returned object.
Additional resources
One element of interacting with a web application that we haven't looked at yet is uploading files. How does the DrupalWebTestCase browser attach a file to the the image field when filling out an article form? In this lesson we'll look at the helpers that DrupalWebTestCase has for handling file interactions including how to use the sample files provided in core, attaching files to a file upload input element, and submitting a form with files attached. In order to demonstrate this we'll be writing a test that creates a new article node with an image attached by filling out the article node creation form and submitting it.
You can get a list of the dummy/test files that come with SimpleTest using the DrupalWebTestCase::drupalGetTestFiles()
method. Whenever possible I recommend making use of the provided files.
$files = $this->drupalGetTestFiles('image');
$this->verbose(print_r($files, TRUE));
You'll need to tell SimpleTest what type of files you're interested in, and it'll then return an array of the available files of that type. Valid types are: 'binary', 'html', 'image', 'javascript', 'php', 'sql', 'text'. Check out the DrupalWebTestCase::drupalGetTestFiles documentation for more information.
Once you've got the list of files you can attach files to a form element using DrupalWebTestCase::drupalPost()
by specifying the full path to the file as the value for the file or image field in the $data array.
$images = $this->drupalGetTestFiles('image');
$image_realpath = drupal_realpath($images[0]->uri);
$edit = array(
'title' => 'Test Article With Image',
'files[field_image_und_0]' => $image_realpath,
);
If SimpleTest doesn't contain a suitable file you can use your own by including the file with your module, and then using the full path to that file in your tests.
$custom_file = drupal_get_path('module', 'helloworld') . '/tests/test.csv';
$realpath = drupal_realpath($custom_file);
This lesson also covers the use of DrupalWebTestCase::drupalPostAJAX()
, which allows for the simulation of an AJAX request using the SimpleTest browser. The primary difference between it and the drupalPost method is you need to specify the name of the element that triggers the AJAX request.
$this->drupalPostAJAX('node/add/article', $edit, 'field_image_und_0_upload_button');
Additional resources
Due to the tightly coupled nature of much of Drupal 7's code most of the test suite is based around functional tests and not unit tests. However, there are a handful of unit tests in core as well as the DrupalUnitTestCase
class that we can extend to write our own unit tests. Since unit tests are an important part of a complete test suite lets take a look at how we can extend the DrupalUnitTestCase
class and add some unit tests to our module. While functional tests do a good job of telling us that something is broken and we should fix it they often are not very helpful for locating the exact problem. Unit tests on the other hand are much more specific and can generally tell us both that something is wrong, and what is wrong.
Unit tests don't have access to the database or the file system since they are not run in the context of a fully bootstrapped Drupal environment. Note that calling any Drupal functions which attempt to access the database will result in an exception being thrown and cause your tests to fail. This includes functions like watchdog()
, and module_implements()
. However, because Drupal doesn't have to be bootstrapped, and we don't have to create an entirely new environment, unit tests are a whole lot faster to execute than functional tests.
In this lesson we're going to take a look at writing a unit test for a function in our module that is used to convert a length of time in seconds to a string like 1 hour 6 minutes. We'll start by looking at the code that does the conversion, and then come up with a list of known inputs and their expected outputs. Finally, we'll write a unit test by adding a new .test file that contains our unit tests and bombarding our conversion function to make sure it's working properly.
Additional resources
So far all the tests that we've written are testing the functionality of our module in the context of a fresh Drupal install. However, in the real world we're also going to want to have tests that test the content of our application. As well as other things that it's unrealistic to assume we'll also have the time and resources to write a complete installation routine to replicate. Instead, it would be nice if we could test against a clone of our site rather than a from scratch installation of Drupal.
The SimpleTest module included with Drupal 7 core doesn't support this feature. However, the 7.x-2.x version of the module in contrib has a DrupalCloneTestCase
class that we can extend instead of the usual DrupalWebTestCase that operates on a clone of the database from our existing site. Allowing us to test things like, "Does the about page exist.", and other mission critical features of our site. In this lesson we'll take a look at installing the SimpleTest module from contrib, and writing some new tests using the 2.x version of the API in order to test an existing website.
Note that after you install the 2.x version of the SimpleTest module none of the tests we've written so far, or any of the tests from core will be in the list of available tests to run. This is because each tests needs to explicitly declare that it is compatible with the 2.x version of the test suite. This is done in the .info file of the module that provides the tests.
Add this to your .info file:
testing_api = 2.x
As of the time that this video was recorded you also need to apply the patch in this SimpleTest issue. The current version of the patch to use is https://www.drupal.org/files/issues/983266-10-simletest-clone.patch. Without it the DrupalCloneTestCase setup method won't properly clone the tables from your existing site. You should always use whatever the latest working patch is from that issue.
Here's how I patched it:
curl -O https://www.drupal.org/files/issues/983266-10-simletest-clone.patch
patch -p1 < 983266-10-simletest-clone.patch
Additional resources
Patched with patch from issue #983266
In this short series you will learn how to find and evaluate Drupal modules for your project. This first tutorial will show you where to find modules on Drupal.org, and various ways you can use Drupal.org's search tools.
Additional resources
Drupal.org Modules page
Drupal module categories
Similar modules group
Drupal case studies
Planet Drupal
An open source project’s strength comes from the power of its base of contributors, and a Drupal project is no different. Although every line of code added or changed in Drupal core goes through rigorous peer review, contributed modules are more of a Wild West where anyone who jumps through a few basic hoops can add modules for everyone to download. Whether or not a module is well maintained, its overall code quality, and how well used it is in the overall community are all important factors for you to consider when selecting modules. This tutorial will talk about determining these factors by closely inspecting the tools Drupal.org provides, starting with the central feature of all Drupal modules: the project page.
By far, the best way to keep up-to-date on which modules are the most useful, and to ensure that those modules do what you need, is to actually get directly involved and help. The Drupal community offers a myriad of ways for everyone, from the person who just installed Drupal for the first time yesterday to the person who has been coding since she was in diapers, to give something back. In this tutorial we'll look at all of these options and explain how you can dive in.
Additional resources
Honeypot module helps you stop spammers in a different way than the typical spam modules that put up barriers, like captchas. Spambots will typically hit a form like the user registration page, fill in all the fields, and submit the form. Honeypot adds a hidden field to the form that users won't see, but spambots will. If that form is filled in, you know you've found a bot and the submission is discarded. This tutorial will give you an overview of the Honeypot module, including the configuration and seeing how it works. This tutorial is based on a Lullabot.com Module Monday post.
Additional resources
Drupal's revisioning system is really powerful. Out of the box we can keep track of changes in our content and restore to a previous version with just a couple clicks. However, on large sites with a lot of activity in their content, revisions can grow exponentially up to a size that it can compromise performance and disk storage. The Node Revision Delete module can help us to keep this under control. In this tutorial we'll walk through an overview of this helpful module. This tutorial is based on a Lullabot Module Monday article.
Additional resources
Lullabot.com Node Revision Delete article
Node Revision Delete module
Drupal's custom fields allow site builders to tweak out their content types with all kinds of data: phone numbers, file uploads, maps, and more. When it comes to tabular information, though, most of us fall back on simple HTML tables in the body field. The TableField module aims to fix that by storing and editing data tables with a single consolidated Drupal field type. In this tutorial we'll give you an overview of how to use the TableField module. This tutorial is based on a Lullabot Module Monday article.
Additional resources
Many businesses, both large and small, would like to take better advantage of their web presence by selling their products or services directly online. Setting up ecommerce, however, can be a very daunting task. In this series you will create an online t-shirt shop, and in the process you will learn how to use the basic pieces of Drupal Commerce, including setting up a Paypal payment system, along with an introduction to the Feeds module, for importing our catalog, and the Rules module, to set up taxes. In this lesson we'll kick things off by reviewing the requirements for the Sweet Tees store, and discuss how we'll be going about implementing them.
Additional resources
Drupal Commerce is a complete package for running an online store. As such, it actually contains numerous submodules that each implement features of an online store, and can be turned on or off depending on the precise functionality required. In this section, we’ll look at each module in turn, and outline its purpose and where it fits. In this lesson we'll get an overview of the major components of Drupal Commerce, including:
- Carts, checkout, taxes, and payment
- Customers and orders
- Products and pricing
- Additional Drupal Commerce add-ons
Additional resources
Before we get to the first step in creating our store—adding products—it’s worth taking some time to discuss and understand how Drupal Commerce treats products within the system. While product management may appear unintuitive at first, the product management features in Drupal Commerce are designed to allow for maximum flexibility. In this lesson we'll explain what product types are, and take a look at both the required fields that come with them, and the custom fields you can add.