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 tutorial, I'll show you a few ways we can create a basic API by exposing data from our Drupal site.
The methods I'll use include:
- Views Datasource
- Custom code
- Services
Self-check question: In what situations would you consider using each of these approaches?
Additional resources
Content Types
TopicA content type is a subtype of the [content entity](link to Entity topic). When a content creator goes to add new content to the site, they are presented with a list of content types to choose from to get the appropriate form to fill out.
Drupal's content moderation and workflow tools allow you to configure and support a flexible multistep publication process.
An overview of some of our favorite Drupal documentation resources.
Fields and the Field API
TopicFields are the building blocks of Drupal's powerful content modeling system. The Field API allows for the development of custom field types to suit almost any data display and collection needs.
Many sites require the ability to upload, manage, and display various assets like files, images, videos, and audio.
Search engine optimization (SEO) is the process of optimizing your online content, so that your content will rank higher in organic (non-paid) search engine results.
Views Module in Drupal
TopicLearn to use data from your Drupal site to create customized lists in various forms, such as bulleted lists, grids, calendars, or sliders.
Debug Drupal Code
TopicDebugging is the practice of troubleshooting an application through the use of tools and processes in order to find and fix bugs, or better understand the underlying code.
Drupal manages information and access for all visitors to your site, called users, through a system of roles and permissions.
Taxonomy
TopicTaxonomy in Drupal provides a way to classify your site’s content. Taxonomy vocabularies are created with terms within those vocabularies.