My PHPUnit configuration for my Drupal projects

The other week, I was asked about how I maintain my PHPUnit config file for my Drupal projects. When running Drupal’s PHPUnit test suites, you typically copy and modify the distribution phpunit.xml.dist file which lives in the web/core subdirectory. There is just one problem. This directory is technically a vendor directory and is replaced during any updates to the drupal/core dependency of your Drupal project. Every minor version will cause your file to reset, or anytime you install dependencies if Drupal core is patched.

One thing I noticed is that since I first followed your tuts a while back, my phpunit.xml is gone. I’m guessing it gets overwritten when core is updated. Do you just keep a copy of the file and paste it in when that happens? Is there a better solution?

I didn’t have time to answer in Slack, so now I’m writing a blog about a better solution. I’ll also share some of my settings that I use — specifically for the database and running a local webserver for functional testing.

If you want the tl;dr, here’s my XML file: https://gist.github.com/mglaman/0baf5ccd7b13b844de105286a04e43d3. If you want to learn about what gets changed and why, read on!

The Automated Testing documentation on Drupal.org says that you should copy the web/core/phpunit.xml.dist to web/core/phpunit.xml. From there you can modify environment variables that PHPUnit will initiate that the tests expect to exist – such as the database connection string and host to access Drupal from for functional tests. Another documented alternative is to just provide those environment variables directly when you run PHPUnit.

The former is simple, until you update the drupal/core package, as I mentioned earlier. The latter is a pain, unless you are running your tests from a bash script which sets the environment variables before the PHPUnit command is executed.

My approach is to take the phpiunit.xml.dist for Drupal and copy it into the root of my project. From there I modify any paths to make them relative to its new location.

Fixing file paths in the PHPUnit configuration file

Hint: all of the work is pretending web/core to existing path definitions.

The bootstrap file

We need to change bootstrap="tests/bootstrap.php" to bootstrap="web/core/tests/bootstrap.php".

The test suite definitions

By default, the testsuites look like this:

<testsuites>
<testsuite name="unit">
<file>./tests/TestSuites/UnitTestSuite.php</file>
</testsuite>
<testsuite name="kernel">
<file>./tests/TestSuites/KernelTestSuite.php</file>
</testsuite>
<testsuite name="functional">
<file>./tests/TestSuites/FunctionalTestSuite.php</file>
</testsuite>
<testsuite name="functional-javascript">
<file>./tests/TestSuites/FunctionalJavascriptTestSuite.php</file>
</testsuite>
<testsuite name="build">
<file>./tests/TestSuites/BuildTestSuite.php</file>
</testsuite>
</testsuites>

The path is relative to the fact the file is normally in the web/core directory. So we need to add web/core to the beginning of each path. Without these definitions, PHPUnit would not be able to detect and execute any of your tests.

<testsuites>
<testsuite name="unit">
<file>./web/core/tests/TestSuites/UnitTestSuite.php</file>
</testsuite>
<testsuite name="kernel">
<file>./web/core/tests/TestSuites/KernelTestSuite.php</file>
</testsuite>
<testsuite name="functional">
<file>./web/core/tests/TestSuites/FunctionalTestSuite.php</file>
</testsuite>
<testsuite name="functional-javascript">
<file>./web/core/tests/TestSuites/FunctionalJavascriptTestSuite.php</file>
</testsuite>
<testsuite name="build">
<file>./web/core//tests/TestSuites/BuildTestSuite.php</file>
</testsuite>
</testsuites>

While it is not often, there is a chance a new test suite could be introduced and your PHPUnit configuration file becomes stale. Since Drupal 8 was released there have only been two test suites added. FunctionalJavascript tests were added pretty early in the life cycle, once a way to run PhantomJS (now Chromedriver via WebDriver) was possible. And, recently, Build tests were added when Drupal’s default project setup went to Composer.

The filters

The final result will look like the following. It sets up directories that should be analyzed for code coverage and has PHPUnit exclude test directories for code coverage.

<!-- Filter for coverage reports. -->
<filter>
<whitelist>
<directory>./web/core/includes</directory>
<directory>./web/core/lib</directory>
<!-- Extensions can have their own test directories, so exclude those. -->
<directory>./web/core/modules</directory>
<exclude>
<directory>./web/core/modules/*/src/Tests</directory>
<directory>./web/core/modules/*/tests</directory>
</exclude>
<directory>./web/modules</directory>
<exclude>
<directory>./web/modules/*/src/Tests</directory>
<directory>./web/modules/*/tests</directory>
<directory>./web/modules/*/*/src/Tests</directory>
<directory>./web/modules/*/*/tests</directory>
</exclude>
<directory>./web/sites</directory>
</whitelist>
</filter>

Note: as of PHPUnit 9 it looks like this is now replaced for the coverage element. At the time of writing, Drupal 9.1.0 has not started its alpha phase and only supports PHPUnit ^8.4.1.

Configuring Drupal test environment variables

  • SIMPLETEST_DB: this is the connection string used to install the database for Kernel, Functional, and FunctionalJavascript tests.
  • SIMPLETEST_BASE_URL: Functional and FunctionalJavascript tests interact with a fully installed Drupal site, and this URL is how the test site should be accessed.

In the renaissance of local development stacks, I really like to make my testing environment simple. I use SQLite for my database and PHP’s built-in web server.

I use the following for my database connection string:

<env name="SIMPLETEST_DB" value="sqlite://localhost/sites/default/files/.ht.sqlite"/>

No database service or software needed. It’s still super fast.

I use the following for my base URL:

<env name="SIMPLETEST_BASE_URL" value="http://127.0.0.1:8080"/>

From my project root I will run the built-in server on port 8080.

php -S 127.0.0.1:8080 -t web

I use the default WebDriver configuration for Chromedriver. I’ll run the built-in server in one terminal window and Chromedriver in another.

🙌 Easy local tools for running the tests.

The finished configuration file

<?xml version="1.0" encoding="UTF-8"?>

<phpunit bootstrap="web/core/tests/bootstrap.php" colors="true"
beStrictAboutTestsThatDoNotTestAnything="true"
beStrictAboutOutputDuringTests="true"
beStrictAboutChangesToGlobalState="true"
printerClass="\Drupal\Tests\Listeners\HtmlOutputPrinter">
<php>
<ini name="error_reporting" value="32767"/>
<ini name="memory_limit" value="-1"/>
<env name="SIMPLETEST_BASE_URL" value="http://127.0.0.1:8080"/>
<env name="SIMPLETEST_DB" value="sqlite://localhost/sites/default/files/.ht.sqlite"/>
<env name="BROWSERTEST_OUTPUT_DIRECTORY" value=""/>
<env name="MINK_DRIVER_CLASS" value=''/>
<env name="MINK_DRIVER_ARGS" value=''/>
<env name="MINK_DRIVER_ARGS_PHANTOMJS" value=''/>
<env name="MINK_DRIVER_ARGS_WEBDRIVER" value='["chrome", {"browserName":"chrome","chromeOptions":{"args":["--disable-gpu", "--no-sandbox", "--headless"]}}, "http://127.0.0.1:9515"]'/>
</php>
<testsuites>
<testsuite name="unit">
<file>./web/core/tests/TestSuites/UnitTestSuite.php</file>
</testsuite>
<testsuite name="kernel">
<file>./web/core/tests/TestSuites/KernelTestSuite.php</file>
</testsuite>
<testsuite name="functional">
<file>./web/core/tests/TestSuites/FunctionalTestSuite.php</file>
</testsuite>
<testsuite name="functional-javascript">
<file>./web/core/tests/TestSuites/FunctionalJavascriptTestSuite.php</file>
</testsuite>
<testsuite name="build">
<file>./web/core//tests/TestSuites/BuildTestSuite.php</file>
</testsuite>
</testsuites>
<listeners>
<listener class="\Drupal\Tests\Listeners\DrupalListener">
</listener>
<!-- The Symfony deprecation listener has to come after the Drupal listener -->
<listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener">
</listener>
</listeners>
<!-- Filter for coverage reports. -->
<filter>
<whitelist>
<directory>./web/core/includes</directory>
<directory>./web/core/lib</directory>
<!-- Extensions can have their own test directories, so exclude those. -->
<directory>./web/core/modules</directory>
<exclude>
<directory>./web/core/modules/*/src/Tests</directory>
<directory>./web/core/modules/*/tests</directory>
</exclude>
<directory>./web/modules</directory>
<exclude>
<directory>./web/modules/*/src/Tests</directory>
<directory>./web/modules/*/tests</directory>
<directory>./web/modules/*/*/src/Tests</directory>
<directory>./web/modules/*/*/tests</directory>
</exclude>
<directory>./web/sites</directory>
</whitelist>
</filter>
</phpunit>

And a link to the XML file as a Gist: https://gist.github.com/mglaman/0baf5ccd7b13b844de105286a04e43d3&nbsp;

Open source developer, working with Drupal and building Drupal Commerce.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store