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:
- If they do not involve JavaScript, then you can write Browser tests.
- If they do involve JavaScript, then write a JavaScript test or a Behat test.
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
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
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.
UI tests
I am grouping together Browser tests, JavaScript tests, and Behat tests as UI tests because they all test the user interface through different methods. I want to save you the hassle of going through each of them by suggesting that if you need to test the user interface, then install and use Drupal Behat Extension. Here is why:
Browser tests don’t support JavaScript. Unless you are testing an administration form, chances are that you will need JavaScript to run on a page. They are solid, but I don’t see a reason to write a Browser test when I could write a Behat one.
JavaScript tests only support PhantomJS as the browser. PhantomJS is headless, meaning that there is no visual interface. It is tedious and at times frustrating to write tests using PhantomJS because you need to type and execute commands on a debugger in order to figure out what the test “sees” at a given point, while with Behat you can use full-featured Chrome or Firefox (I laugh at myself every time I call this a "bodymore" browser) so debugging becomes way more fun. Moreover, these kind of tests get randomly stuck both in my local development environment and in Jenkins.
Discovering Behat tests
Andrew Berry and myself spent a lot of time trying to get JavaScript tests working locally and in Jenkins without luck, which is why we decided to give Behat tests a go. It felt like salvation because:
- The setup process of the Drupal Behat Extension module is straightforward.
- We found no bugs in the module while writing tests.
- We can use Chrome or Firefox locally, while Jenkins uses PhantomJS and does not get stuck like it did with JavaScript 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:
- [PP-2] JavascriptTests with webDriver aims to replace gastonjs by WebDriver, which would mean that you could use Chrome or Firefox locally instead of just PhantomJS.
- The PHPUnit initiative, whose goal is to get rid of Simpletest in Drupal 9.
- Experiment: Get nightwatch working to functional test Drupal JS via JS would allow to write functional tests in JavaScript.
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.
Acknowledgements
I want to thank the following folks for their help while writing this article:
- Matt Oliveira, for proofreading.
- Marcos Cano, for his tips and his time spent doing research on this topic.
- Seth Brown, for helping me to become a better writer.
- The Draco team at Turner, for giving me the chance to experiment and implement testing as part of our development workflow.