Testing, Testing, 1, 0, 2
9 Sep
Stuart Clark
If you haven't read part 1 of this series, I recommend doing so now, as the example tests below are going to be based the scenario developed in that post: http://realityloop.com/blog/2015/08/19/testing-testing-1-0-1
In this post I will be covering how to write Simpletests, specifically targeted at our previous scenario using an installation profile based site.
Next time I'll cover over Behat tests, which are much nicer for the non-technical user to write, as well as wrapping the whole lot with Continuous Integration via Travis CI.
Preparing your tests
Simpletests live in your Drupal Installation profile or module(s) as .test files, which contain PHP classes. As such, you not only need to create the files, but you need to register them in your projects .info file.
My own personal preference is to keep all my simpletests in a tests directory, which I will be doing so in the example below.
- Create a tests directory in your sites install profile directory.
- Create a project prefixed test file in the tests directory.
e.g., profiles/fictitious/tests/fictitious.test
- Populate that file with a basic Simpletest stub:
- <?php
- /**
- * @file
- * Tests for the Fictitious Inc. installation profile.
- */
- /**
- * Class FictitiousTestCase
- */
- class FictitiousTestCase extends DrupalWebTestCase {
- /**
- * @inheritdoc
- */
- public static function getInfo() {
- return array(
- 'name' => 'Fictitious Inc.',
- 'description' => 'Test Fictitious Inc. functionality',
- 'group' => 'Fictitious Inc.',
- );
- }
- }
- Register your test file by adding the following line to your projects .info file (e.g., profiles/fictitious/fictitious.info):
files[] = tests/fictitious.test
- Flush Drupal cache.
- Enable the Testing module.
- Navigate to the Testing UI: admin/config/development/testing
Assuming all went as planed, you should see your tests in the Tests table.
However, your test won't actually do anything yet...
Write your tests
A simpletest consists of three main parts:
Definition - getInfo()
This required function simply returns a keyed array with information about the test; it’s name, description and group.
This has already been defined in the above test stub, and is relatively straightforward.
Setup - setUp()
An optional function that is run before each test function allowing you to enable modules, create users or any other common tasks required for each test.
It's unusual not to need this, as at the minimum you will need to enable specific modules for the purpose of testing. For the case of our scenario we need to do more than the normal as we need to ensure that our Install profile is used:
- protected $profile = 'fictitious';
- public $auth_user = NULL;
- /**
- * @inheritdoc
- */
- public function setUp() {
- // Setup required modules.
- $info = system_get_info('module', 'fictitious_core');
- $modules = array('fictitious_core') + $info['dependencies'];
- parent::setUp($modules);
- // Flush caches and revert features.
- _fictitious_core_flush_revert();
- // Create an authenticated user.
- $this->auth_user = $this->drupalCreateUser(array('access site reports'));
- }
Taking a closer look at the above, you’ll see the first thing we do is set the installation profile which is used in the parent classes setUp() function:
protected $profile = 'fictitious';
Next we stub a variable to be used for the authenticated user, which will be created during the actual setUp() function:
public $auth_user = NULL;
We then declare our setUp() function and get to the important part. The first thing we do is to build a list of modules and pass them through to the parent function, which will deal with all the heavy lifting.
As we are dealing with an installation profile that simply enables a Feature module, I did find that not all the required modules are actually being enabled correctly, so to compensate for that the first two lines of code actually extract the modules list from the Features info file before being passed back to the parent function:
- $info = system_get_info('module', 'fictitious_core');
- $modules = array('fictitious_core') + $info['dependencies'];
- parent::setUp($modules);
Next, again to compensate for a Features based installation profile, we need to flush all caches and revert all Feature components to ensure that we start with out configuration set properly. To do this I use a function that I use repeatedly during deployment in all the sites I work on: https://github.com/Realityloop/fictitious/blob/7.x-1.x/modules/custom/fictitious_core/fictitious_core.install#L9
Lastly, we need to create an authenticated user with the permission to do what will be required during the tests. In this case, all that user will need to do is to look at the watchdog logs to ensure that the emails would be sent to the correct recipients and contain the correct information.
To do this we use the drupalCreateUser() method, which takes an array of permissions as it's only argument:
$this->auth_user = $this->drupalCreateUser(array('access site reports'));
Test(s) - test*()
Finally, the most important parts, the actual tests. A single SimpleTest class can contain as many tests as you desire, each a member function prefixed with test.
e.g., public function testContactForm() {}
It is worth noting that each test function will be wrapped with the setup and teardown of a Drupal installation, so it may be of more benefit to do more actual tests in a single function when possible.
A test consists of two main parts: Instructions and Assertions. Remember that you simply want to automate the manual tests you had been doing in the past:
Instruction:
Goto the frontpage of the website
Code:
$this->drupalGet('');
Assertion:
Is the link to the 'Contact' page available.
Code:
$this->assertLink(t('Contact'), t('Link to the "Contact" page is available.'));
In our case we want to submit some data to our Contact form and then check the contents of the sent email(s), which is a slightly more complex case than the above example, but still fits in with the basic concept:
1. Submit the form with dummy data.
Dummy data is generated using the randomName() method, which will generate an 8 character random string of containing letter and numbers.
The drupalPost() method is used to submit the form, which takes three required arguments:
- $path - The path of the form we are submitting.
- $edit - A keyed array of the field values we are submitting, the key being the field name attribute.
- $submit - The label of the submit button being clicked.
- $edit = array(
- 'field_contact_type[und]' => 'support',
- 'field_first_name[und][0][value]' => $this->randomName(),
- 'field_last_name[und][0][value]' => $this->randomName(),
- 'field_email[und][0][email]' => "{$this->randomName()}@fictitious.inc",
- 'field_contact_subject[und][0][value]' => $this->randomName(),
- 'field_contact_message[und][0][value]' => $this->randomName(),
- );
- $this->drupalPost('contact', $edit, t('Submit'));
2. Login as our authenticated
Relatively straightforward, using the drupalLogin() method with our previously created user object as the argument.
$this->drupalLogin($this->auth_user);
3. Navigate to the email log
This is the trickiest part of this specific test case, as we need to determine the url of the specific watchdog log for our emails.
To do this, the first thing need to do is to navigate to the watchdog logs page itself:
$this->drupalGet('admin/reports/dblog');
Thankfully, whenever a drupalGet() or drupalPost() method is invoked, a copy of the rendered markup of that page is saved as an HTML file and linked to from the Simpletest log, allow us to see what Simpletest sees:
Using this, we are able to get the actual URLs of the two emails that are sent, being: admin/reports/event/91 and admin/reports/event/92.
We can then adjust the above drupalGet() call to go directly to one of our email logs:
$this->drupalGet('admin/reports/event/91');
4. Assert that the email content is all correct
Now that we've pulled up our email, we need to run a few assertions to make sure that the the email being sent is exactly what we expect it to be. Before doing so, it's worth running the test and looking at the output of the email log so we can see what data is available for our assertions.
As we are dealing with raw text, the best choice of assertion is either: assertText() or assertRaw().
- $this->assertText("to: support@fictitious.inc", t('Support email sent to correct email.'));
- $this->assertRaw("[from] => {$edit['field_email[und][0][email]']}", t('Support email sent from correct email.'));
All together now:
- public function testContactForm() {
- $edit = array(
- 'field_contact_type[und]' => 'support',
- 'field_first_name[und][0][value]' => $this->randomName(),
- 'field_last_name[und][0][value]' => $this->randomName(),
- 'field_email[und][0][email]' => "{$this->randomName()}@fictitious.inc",
- 'field_contact_subject[und][0][value]' => $this->randomName(),
- 'field_contact_message[und][0][value]' => $this->randomName(),
- );
- $this->drupalPost('contact', $edit, t('Submit'));
- $this->drupalLogin($this->auth_user);
- $this->drupalGet('admin/reports/event/91');
- $this->assertText("to: support@fictitious.inc", t('Support email sent to correct email.'));
- $this->assertRaw("[from] => {$edit['field_email[und][0][email]']}", t('Support email sent from correct email.'));
- }
More information
There really is too much information to cover over in a single post, for more information I recommend the following resources:
- Testing (simpletest) Tutorial (Drupal 7) - https://www.drupal.org/simpletest-tutorial-drupal7
- Assertions - https://www.drupal.org/node/265828
Conclusion
Simpletests look scarier than they are, but when it comes down to it the majority of a simpletest can be copied from a previous simpletest.
It really is as simple as using drupalGet() or drupalPost() to simulate your manual tests, and then using the correct assertions for the situation. And like any new language, it gets easier over time.
But next time we'll look at Behat, which is 10 times easier to write as it uses human language for it's tests.
drupaldrupal planettesting