Drupal automated testing: an introduction

By Oliver Davies
Oliver Davies headshot

Drupal expert Oliver Davies talks you through automated testing methodology, approaches, and terminology, and explores how to take a test-driven development (TDD) approach to creating a new Drupal module.

What is automated testing?

Automated testing is a way of verifying that your code works in the way you expect

Drupal 8 and 9 use PHPUnit, which is the same testing framework used in other PHP frameworks including Symfony and Laravel. There has been a particular emphasis on automated testing in Drupal core during the Drupal 8 lifecycle, with all new functionality and bug fixes requiring automated tests to be committed to core, and with an increasing number of contributed modules doing the same.

Writing automated tests for our custom code is very important as it demonstrates that the functionality works, not only when the tests and code are written, but also that it continues to work as the codebase grows and becomes more complex as new features are added. It also helps to demonstrate that functionality works when dependencies change, such as updating Drupal itself to a new minor or major version. 

If a test that passed previously has since started to fail, it needs to be reviewed and fixed before the change is pushed to the client’s website.

How do I get started with automated testing in Drupal?

Drupal core already includes the PHPUnit testing framework, so it’s already there when you download Drupal or install Drupal via Composer.

Within the ‘core’ directory there is a file called phpunit.xml.dist that configures PHPUnit. You can create your own from scratch, or use that as a template. 

As a minimum, you’ll need to provide details of how to connect to the database (the tests will need to be able to install Drupal) and a base URL to connect to (this will be different based on your local environment).

Once you have tests in place, Drupal.org has a tool called Drupal CI that can run those tests automatically, so you can be sure that a submitted patch hasn’t broken them and check that any new tests pass or fail. For your custom code, you can use a tool like GitHub Actions, Jenkins, Travis CI, or Circle CI to do the same thing. We do this for each pull request, in the same way that Drupal CI does for each patch file.

What are the types of automated tests in Drupal?

There are different types of automated tests for Drupal and your use of these is dictated by the nature of what you want to test.

  • Functional tests (web, feature): these test behaviour and functionality. They make HTTP requests to the website, and have access to the database and other services via the service container. This type of test is slower to run.
  • FunctionalJavascript: these are functional tests, but have access to run JavaScript.
  • Kernel (integration): this test has no browser capabilities, but has access to the database and other services. It requires a bit more configuration than other test types.
  • Unit: this type of test does not have access to the database or the service container, so all dependencies need to be mocked. This method is fast to run.

How should I approach Drupal automated testing?

You can structure your test in the following steps:

  • Arrange: set up the environment, create users, nodes, and set up dependencies
  • Act: perform an action, such as going to a page
  • Assert: verify that something happened, such as you get a particular response code returned or that you see some expected text on the page

Use either of the following approaches:

  • Inside-out (testing pyramid): this mostly unit tests, with some integration tests, and few functional tests.
  • Outside-in (testing trophy): this mostly handles functional tests, with some integration tests, and few unit tests. It’s more flexible and easier to refactor.

I tend to prefer the outside-in approach, as I’ve found that it’s quicker to get a test to pass, and easier to refactor the code once it’s working.

What is test-driven development?

Test-driven development is where failing tests are written before your production code. You write code to make the test pass, and refactor once that testing is passed. This should result in cleaner, simpler code as you’ve only written the code that it needed to make the tests pass.

If you’re following Uncle Bob’s three laws of TDD, you only write enough of a test to make it fail, and then write enough code to make it pass. Once it’s passing, you can refactor and then go through the loop again by continuing to expand the same test or starting a new one.

Teaching Drupal automated testing at DrupalCamp London

I've been a regular attendee and speaker at DrupalCamp London ever since its second year. I’m also an automated testing enthusiast and often write on the topic. So when I was approached to deliver a workshop at DrupalCamp London 2020, I had no trouble settling on a topic!  

When I deliver presentations on automated testing, I usually start by introducing concepts and approaches to testing. I’ll then move onto showing people how to build a blog module in Drupal 8 while writing tests and practicing test-driven development (where the tests are written before the production code). 

I decided to do the same thing for the DrupalCamp London workshop, but rather than explaining everything through slides, I wanted to provide hands-on instructions that would allow people to work through the steps at their own pace. This approach would also allow remote participants to have a similar experience.

If you’re interested in teaching or learning how to do automated testing with Drupal 8, the below ‘lesson plan’ from my DrupalCamp London workshop will have you on your way!

You can also find my complete workshop plan and exercises on GitHub, along with the example code, if you want to work through this yourself.  

Here’s what’s included:

Functional tests for Drupal core functionality

  • Learn how to write and run a test, and see the success or failure message. 

Building a blog

  • Once you feel comfortable with the testing process, move onto building a blog module and outside-in, test-driven development.
  • With your acceptance criteria in hand, start again with another functional test to ensure that your blog page exists, and let those tests drive you to creating a routing file and a Controller for the page. 
  • Try going through a few TDD loops at this stage, getting failing tests to pass before adding more assertions and going through the cycle again. 
  • Once you’re happy that anonymous users can access and view the blog page, move onto showing the articles themselves.

Testing with an article repository

  • Start looking at kernel (integration) tests, and how you can use an article repository to get blog posts from the database, as well as exploring other concepts such as services and autowiring.
  • Test a blog page by getting your hands on some articles! Explore how to do this within your tests using different test classes to extend, and traits to create some articles. Review and tackle some of the more cryptic error messages that you sometimes get with kernel tests.
  • Once you have the correct content being returned, try adding some additional tests to ensure that only published articles are returned and that they’re returned in the desired order. Used these to improve your blog repository and make it less brittle.

Final thoughts

Having a suite of automated tests as I write and maintain contributed modules has been truly invaluable. It’s given me the confidence to add new features, fix bugs, and refactor code – without worrying about breaking a lot of Drupal websites! 

The more tests you write, the more confident you can be that your code works correctly. In my case, that translates into better quality for my clients.

Automated tests also allow you to prevent and identify bugs within your custom code and quickly fix them locally before they get as far as a code review or onto a client’s live site where they’d take longer to fix and potentially cause more problems. 

I hope this post, together with my DrupalCamp lesson plan, gives you the tools and confidence to put testing into practice and start writing better code today.

About the author

Oliver is a full-stack software engineer and has been working with Drupal since 2007. He is an Acquia-certified Drupal 8 Grand Master, core contributor, and contrib project maintainer.

Digital accessibility has never been more important as coronavirus forces our lives online. Tune into our accessibility webinar with Acquia on April 28 to learn how Drupal is paving the way for more inclusive digital experiences.