Helpful Tips for Avoiding Common Problems Creating Simpletests in Drupal

There are a few articles online about how to write simpletest tests for Drupal, but most of them assume you're testing something very simple, and almost none of the examples deal with anything more complicated than a few textfields. So here are some very specific tips on non-obvious things you may want to test.

Exceptions During Setup

If you have module dependencies, you'll need to specify them manually.  Unlike drush, which will prompt you to install or enable any dependencies, simpletest will just enable any modules you specify. This can result in unexpected exceptions, and the error messages are unhelpful and sometimes misleading. Check your dependencies before you go hunting for obscure bugs.

Figuring out the Array Keys for drupalPost

One of the easier things to miss is exactly how you should key the $edit array parameter of drupalPost.  It's always going to be the name of the field.  The easiest way to find this is to inspect the element using your browser's dev tools and find the name attribute of the input field.  Yes, it will have a lot of brackets sometimes. That's expected - enter it exactly as it appears.  Don't add any quotes.  If the name attribute is 

field_my_field[und][0][value]

you'll define it like this:

$edit['field_my_field[und][0][value]']= ...

Image and File Upload Fields

This one is actually pretty simple, but it tripped me up until I realized what was involved. If you're wondering how you can provide a file URL and click submit to upload it, you actually don't have to most of the time. If you provide a file path, submitting the whole form will upload the files as part of the submit.  If you use realpath() that's all that is required, like so:

$edit['field_my_file'] = realpath('sites/default/files/test.png');

Permissions and Uploads

You'll probably run into permissions issues at some point.  If you get failures or exceptions when uploading files that just say "file could not be uploaded", it's probably permissions related.  If you get the errors running the test with drush but not via the UI, here's a handy checklist for how to resolve this:

  • Run the test as the web user. For apache, it's www-data.  When running tests:
    sudo -u www-data drush test-run ...
  • chown sites/default/files and sites/default/private and all subdirectories to www-data. From sites/default:
    sudo find . -type d -exec chown www-data:www-data {} \;
  • chmod directories in files/private to 755. From sites/default: 
    sudo find . -type d -exec chmod 755 {} \;

Specifying the URL

Drush doesn't actually know what the site URL is when you run it, so you'll want to use --l or --uri to specify, like so: 

drush --l=mysite.localhost test-run ...

Viewing the Results

I find that the HTML output of simpletest is absolutely invaluable. It lets you see what the tests see.  But by default, Simpletest appends output to existing files, which can result in an unreadable jumble.  I actually clear out the entire directory as part of my test run.  My entire command looks like this:

sudo rm -rf sites/default/files/simpletest; sudo -u www-data drush --l=mysite.localhost test-run MySiteWebTest --verbose

When I'm testing, I always have a tab open that shows the latest file in sites/default/files/simpletest/verbose.  They're numbered HTML files and they have built-in next/previous links, which makes it easy to navigate forward and back and see what your test sees. I'll typically run a test and then immediately look at the output to find out either what the error/failure page looks like, or what page submitting a form results in.  This can help you catch problems that are with your tests, rather than your code. For example, if you get a failure stating that a field's value couldn't be set, but the field definitely exists on your site, look at the output - maybe you forgot to enable a module responsible for providing that field, or maybe you need to set a variable as part of setup.

Forms that use Inline AJAX to Display Sub-Forms

Inline Entity Forms can make the UX for creating entity references much easier, but it does result in a fairly complex setup for simpletest, and to handle it gracefully you'll need to understand the first parameter of drupalPost.  To illustrate some examples, let's look at a content type, called Location.  Location has an entity reference to an Adventure, which is a custom entity.  It is rendered on the node creation page as an inline entity form.  So when you look at the location field on the create/edit screen, it looks like this:

The dropdown is the list of bundles for adventure.  So how do we get the web test to "click" on the button?  Typically you provide the URL of a page to drupalPost like so:

$this->drupalPost('node/add/location', $edit, 'Add new adventure');

This actually loads the page and submits the form.  But we don't want to load the page first.  By setting the first parameter of drupalPost to NULL, you can submit a form on the test's "current" page.  This lets you easily submit multi-step forms, as long as you use drupalGet to load the page for the test first.

$this->drupalGet('node/add/location');
$edit = array('field_adventures[und][actions][bundle]' = 'biking');
$this->drupalPost(NULL, $edit, 'Add new adventure');

Try this, and then look at the HTML output (in sites/default/files/simpletest/verbose).  You should see the add location page as if you'd clicked on the button.  Continue with this pattern and you can create the adventure, and click the submit button for that form in the same way.

One last caveat for when you actually submit the whole page.  You'll need to use NULL for the last drupalPost as well.  If you specify the URL, it will reload the page, basically throwing out anything you'd entered in a subform.

Final Thoughts

This article hopefully shed some light on challenging aspects of writing web test for Drupal.  I haven't touched on one particular aspect, though - what tests to write.  That's definitely a huge topic and deserves an article all its own. Watch this space for a followup!