Updated: 2021-10-07
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 9 looks like this:
name: drupaltest
recipe: drupal8
config:
php: '7.4'
webroot: ./web
This will give you a set of containers capable of running Drupal 9.
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 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:
Drupal 9
lando composer require drupal/core-dev --dev --with-all-dependencies
lando composer require --dev phpspec/prophecy-phpunit
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://mysite.lndo.site"
SIMPLETEST_DB: "mysql://database:database@database/database"
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"
Modify `SIMPLETEST_BASE_URL` and `SIMPLETEST_DB` to point to your lando site and database credentials as needed. See "Complete .lando.yml file example" section below for an example.
This does three things:
- Adds environment variables to the appserver container (the one we’ll run the tests in).
- Adds a new container for the chromedriver image which is used for running headless javascript tests (more on that later).
- 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_DB` environment variable and specified a MySQL database which needs to exist as a container (another service definition). 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:
- Set up a set of Docker containers using Lando to host the Drupal 9 codebase and downloaded the needed PHP packages.
- Modified the `.lando.yml` config to add environment variables and the chromedriver container
- 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.4'
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://projectname.lndo.site" # Change projectname to the same as the name key above.
SIMPLETEST_DB: "mysql://database:database@database/database"
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"