I have been working with Turner for the past twelve months as part of the Draco team (Drupal Application Core), who maintains Drupal 8 modules used by TV channels such as NBA, PGA, or TBS. Once brands started to use our modules, it was mandatory for us to improve testing coverage and monitoring. By that time, we had unit tests, but there was still a long way to go to get to a point where we could feel safe to merge a given pull request by looking at the code and the test results in Jenkins. This article explains our journey, including both the frustrations and successes implementing testing coverage in Drupal 8.
General structure of a test
A test tests something. Yes, it may sound trivial, but I want to use this triviality to explain why there are different types of tests in Drupal 8 and how they achieve this goal. Every test has a first step where you prepare the context and then a second step where you run assertions against that context. Here are some examples:Context Tests I am an editor who has created a page node. When I open the full display view, I should see the title, the body, and an image I am visiting the homepage. There should be a menu with links to navigate to the different sections of the site, plus a few blocks promoting the main sections. I am an authenticated user who has opened the contact form. Some of the fields such as the name and email should be automatically set. Form submission should pass validation. A confirmation message is shown on success.
The assertions in the Tests column verify that the code that you have written works as expected under a given Context. To me, the end goals of tests are:
- They save me from having to test things manually when I am peer reviewing code.
- They prove that a new feature or bug fix works as expected.
- They check that the new code does not cause regressions in other areas.
Having said that, let’s dive into how we can accomplish the above in Drupal 8.
Types of tests available
Depending on what you want to test, there are a few options available in Drupal 8:
- If you want to test class methods, then you can write Unit tests.
- If you want to test module APIs, then your best option is to write Kernel tests.
- If you want to test web interfaces (I call these UI tests), then you have a few options:
In the following sections we will see how to write and run each of the above, plus a few things to take into account in order to avoid frustration.
Unit tests in Drupal 8 are awesome. They use the well known PHPUnit testing framework and they run blazingly fast. Here is a clip where we show one of our unit tests and then we run the module’s unit test suite:
The downside of Unit tests is that if the class has many dependencies, then there is a lot that you need to mock in order to prepare the context for your test to work, which complicates understanding and maintaining the test logic. Here is what I do to overcome that: whenever I see that I am spending too much time writing the code to set up the context of a Unit test, I end up converting the test to a Kernel test, which we explore in the next section.
Kernel tests are Unit tests on steroids. They are great when you want to test your module’s APIs. In a Kernel test, Drupal is installed with a limited set of features so you specify what you need in order to prepare the context of your test. These tests are written in PHPUnit, so you also have all the goodness of this framework such as mocking and data providers.
Here is a Kernel test in action. Notice that it is very similar to the clip above about Unit tests:
Kernel tests fetch the database information from core/phpunit.xml where you need to set the database connection details. You may have noticed that these tests are slightly slower than Unit tests—they need to install Drupal—yet they are extremely powerful. There are many good examples of Kernel tests in Drupal core and contributed modules like Token.
Discovering Behat tests
- The setup process of the Drupal Behat Extension module is straightforward.
- We found no bugs in the module while writing tests.
Here is a sample test run:
On top of the above, I discovered the usefulness of feature files. A feature file contains human readable steps to test something. As a developer, I thought that I did not need this, but then I discovered how easy it was for the whole team to read the feature file of a module to understand what is tested, and then dive into the step implementations of that feature file to extend them.
Here is a sample feature file:
Each of the above steps matches with a class method that implements its logic. For example, here is the step implementation of the step “When I create a Runsheet”:
Behat tests can access Drupal’s APIs so, if needed, you can prepare the context of a test programmatically. All that Behat needs in its "behat.yml" file is the URL of your site and the Drupal’s root path:
Now look at your project
Does your project or module have any tests? Does it need them? If so, which type of tests? I hope that now you have the answers to some of these questions thanks to this article. Go ahead and make your project more robust by writing tests.
If you are willing to improve how testing works in Drupal 8 core, here are a few interesting open issues:
- The PHPUnit initiative, whose goal is to get rid of Simpletest in Drupal 9.
In the next article, I will share with you how do we run Unit, Kernel, and Behat tests automatically when a developer creates a pull request and report their results.
I want to thank the following folks for their help while writing this article: