How to run Drupal’s PHPUnit tests in Lando

Setting up Lando and Drupal to run PHPUnit tests can be tricky. This post attempts to break it down and provides a guide to getting set up.

At the end of the post is a working .lando.yml file that you can copy/paste from to extend your usual Lando set up.

A bit about Drupal tests

Drupal 8 and 9 use PHPUnit to run various types of test. This post won't go into detail about writing tests but instead focuses on getting an environment capable of running tests.

Each time a test is run a new environment is set up, including the database, the tests are run and then the test environment is destroyed. While developing a Drupal module or site it can be useful to write tests and run them in your development environment. Instead of needing to run two MySQL databases side by side, it can be sufficient to have your test environment build the Drupal site using SQLite as the backend database. This allows you to keep the two environments from clashing but still have them run in the same containers for ease of use.

Lando set up

Lando is a very nicely written bit of software which acts as a wrapper over docker-compose and provides a slightly simpler format to help you get a development environment up and running.

A very basic .lando.yml configuration file for Drupal 8 looks like this:

name: d8test
recipe: drupal8
config:
  php: '7.2'
  webroot: ./web

This will give you a set of containers capable of running Drupal 8. At the time of writing PHP 7.2 has the best compatibility with Drupal 8’s version of PHPUnit and dependencies but it is possible to upgrade packages for compatibility.

To get started create a .lando.yml file in a directory of your choosing with the content above. You might want to change the name value or add further config. The location where you create the .lando.yml file will be the project root (not the web root) and we’ll run most of the commands from this location.

Bring the containers up so we have access to lando’s composer and php generally:

lando start

Drupal 8 installation

To start with we'll install Drupal using the current recommended approach. Due to the current directory already having our .lando.yml file it in composer won't be able to install into it. Instead we'll install into a temp directory and then mv everything out of it into our current directory and clean it up afterwards:

lando composer create-project drupal/recommended-project temp
mv temp/* temp/.* . ; rm -r temp

The drupal/recommended-project does not include the require-dev section that comes with the git installed version, so we need to add the development packages ourselves:

lando composer require behat/mink:1.7.x-dev behat/mink-goutte-driver:^1.2 behat/mink-selenium2-driver:1.3.x-dev composer/composer:^1.9.1 drupal/coder:^8.3.2 jcalderonzumba/gastonjs:^1.0.2 jcalderonzumba/mink-phantomjs-driver:^0.3.1 mikey179/vfsstream:^1.6.8 "phpunit/phpunit:^6.5 || ^7" phpspec/prophecy:^1.7 symfony/css-selector:^3.4.0 symfony/phpunit-bridge:^3.4.3 symfony/debug:^3.4.0 justinrainbow/json-schema:^5.2 symfony/filesystem:~3.4.0 symfony/finder:~3.4.0 symfony/lock:~3.4.0 symfony/browser-kit:^3.4.0 --dev

If you are running PHP 7.3 you'll need to update a few packages:

lando composer update phpunit/phpunit symfony/phpunit-bridge phpspec/prophecy symfony/yaml --with-dependencies

Configure Lando for PHPUnit tests

We now need to configure Lando with some environment variables which Drupal's PHPUnit implementation will make use of.

First of all copy the phpunit.xml.dist file into the project root:

cp web/core/phpunit.xml.dist phpunit.xml

We'll leave the defaults for most of the values in that file except for where to find the bootstrap.php file. This should be changed to the path in the Lando container, which will be /app/web/core/tests/bootstrap.php. This can be done with sed:

sed -i 's|tests\/bootstrap\.php|/app/web/core/tests/bootstrap.php|g' phpunit.xml

All the other variables we will leave as default as we'll be passing in environment variables through Lando to the containers which will take precedence over the values in the phpunit.xml file.

Next edit the .lando.yml file and add the following:

services:
  appserver:
    overrides:
      environment:
        SIMPLETEST_BASE_URL: "http://appserver"
        SIMPLETEST_DB: "sqlite://localhost/tmp/db.sqlite"
        MINK_DRIVER_ARGS_WEBDRIVER: '["chrome", {"browserName":"chrome","chromeOptions":{"args":["--disable-gpu","--headless"]}}, "http://chrome:9515"]'
  chrome:
    type: compose
    services:
      image: drupalci/webdriver-chromedriver:production
      command: chromedriver --log-path=/tmp/chromedriver.log --verbose --whitelisted-ips=
tooling:
  test:
    service: appserver
    cmd: "php /app/vendor/bin/phpunit -c /app/phpunit.xml"

This does three things:

  1. Adds environment variables to the appserver container (the one we’ll run the tests in).
  2. Adds a new container for the chromedriver image which is used for running headless javascript tests (more on that later).
  3. A tooling section which adds a test command to lando to run our tests.

Important!

After updating the .lando.yml file we need to rebuild the containers with:

lando rebuild -y

Running tests

At this point we’re actually ready to run existing tests. Even though Drupal hasn't been installed it is possible to run tests as tests are always run in a separate database. We defined this database in the SIMPLETEST_BASE_URL environment variable and specified an SQLite database, stored in the containers /tmp/ directory. This database is destroyed after each test to ensure a clean environment.

Lets run a single test from the color module:

lando test web/core/modules/color/tests/src/Functional/ColorConfigSchemaTest.php

This should produce the following output:

PHPUnit 7.5.20 by Sebastian Bergmann and contributors.

Testing Drupal\Tests\color\Functional\ColorConfigSchemaTest
.                                                                   1 / 1 (100%)

Time: 18.94 seconds, Memory: 6.00 MB

OK (1 test, 8 assertions)

Success! You've run your first PHPUnit functional (browser) test in a Lando container.

It will have taken a little while to run, maybe around 20 seconds. This is because a full Drupal site is installed temporarily and specifically for this test.

Running a JavaScript test

By installing the chromedriver image in a container we can also run headless browser tests with JavaScript. Let’s try one:

lando test web/core/modules/action/tests/src/FunctionalJavascript/ActionFormAjaxTest.php

You should again see similar output:

PHPUnit 7.5.20 by Sebastian Bergmann and contributors.

Testing Drupal\Tests\action\FunctionalJavascript\ActionFormAjaxTest
.                                                                   1 / 1 (100%)

Time: 24.99 seconds, Memory: 6.00 MB

OK (1 test, 7 assertions)

Running a Kernel test

Finally lets run a quick kernel test which doesn't run in a browser:

lando test web/core/modules/help/tests/src/Kernel/HelpEmptyPageTest.php

Kernel tests install an empty database and so the bootstrap phase is very quick compared to a full installation and the test will execute much quicker.

Wrap up

By following these steps you’ve:

  1. Set up a set of Docker containers using Lando to host the Drupal 8 codebase and downloaded the needed PHP packages.
  2. Modified the .lando.yml config to add environment variables and the chromedriver container
  3. You've run three PHPUnit tests, the first an internal browser test using Mink, the second a headless chromedriver based test and the third a kernel test without a headless browser.

You can now install Drupal if you want, using your usual methods. You'll need the Lando database container's credentials (or supply your own) and begin developing as you normally would. In addition you now have access to PHPUnit tests and can start writing and running your own tests as you need.

Complete .lando.yml file example

A more complete looking .lando.yml might look like this:

name: projectname # Change this
recipe: drupal8
config:
  xdebug: true
  webroot: web
  php: '7.3'
proxy:
  mailhog:
    - mail.projectname.lndo.site # Change projectname to the same as the name key above.
  adminer:
    - adminer.projectname.lndo.site # Change projectname to the same as the name key above.
services:
  appserver:
    overrides:
      environment:
        SIMPLETEST_BASE_URL: "http://appserver"
        SIMPLETEST_DB: "sqlite://localhost/tmp/db.sqlite"
        MINK_DRIVER_ARGS_WEBDRIVER: '["chrome", {"browserName":"chrome","chromeOptions":{"args":["--disable-gpu","--headless", "--no-sandbox"]}}, "http://chrome:9515"]'
        DRUSH_OPTIONS_ROOT: '/app/web'
        DRUSH_OPTIONS_URI: 'http://projectname.lndo.site' # Change projectname to the same as the name key above.
  database:
    creds:
      user: database
      password: database
      database: database
  mailhog:
    type: mailhog
    hogfrom:
      - appserver
    portforward: true
  adminer:
    type: compose
    services:
      image: dehy/adminer
      command: '/bin/s6-svscan /etc/services.d'
    portforward: true
  chrome:
    type: compose
    app_mount: false
    services:
      image: drupalci/webdriver-chromedriver:production
      command: chromedriver --log-path=/tmp/chromedriver.log --verbose --whitelisted-ips=
tooling:
  test:
    service: appserver
    cmd: "php /app/vendor/bin/phpunit -c /app/phpunit.xml"
Back to blog