PHPUnit up and running with Drupal, DDEV and PHPStorm

There are some articles out there about this topic: like the official DDEV docs PHPStorm – Enabling PHPUnit or Matt Glaman’s Running Drupal’s PHPUnit test suites on DDEV, but I haven’t found it so easy to figure it out. So, I’m writing this guide, although knowing that one day it will become obsolete, like it always have been with guides and new versions of things 🙂

First step: enabling DDEV integration plugin for PHPStorm

DDEV has a PHPStorm plugin that eases a lot working with DDEV. One of the great things DDEV’s plugin does is creating a CLI interpreter that executes PHP code inside the web container. This will be the cornerstone of our setup.

Setting DDEV as the project’s CLI interpreter and the Test framework interpreter

Under Settings -> PHP section, we can now select the DDEV CLI interpreter:

If you don’t see the interpreter, check that you have a Docker connection added in Build, Execution, Deployment -> Docker:

And check that “Use Compose V2” is enabled under the Tools subsection:

Now, we must go to PHP -> Test Frameworks and add a new one that uses the DDEV CLI interpreter:

We must also set a custom Default configuration file named phpunit.xml and located in our project’s root directory. DDEV’s CLI interpreter runs inside the web docker container, so we must set the path as it’s seen inside the container: /var/www/html/phpunit.xml. (this file doesn’t exist yet, we will create it later).

Now, we have to tell PHPStorm to run this project’s tests with this new test framework. Go to Run -> Edit configurations menu and clear any configuration you may find here.

Then, click on Edit configuration templates… -> PHPUnit and set DDEV as the Command Line Interpreter:

Creating project’s PHPUnit configuration file

Copy core’s PHPUnit configuration file /web/core/phpunit.xml.dist to /phpunit.xml, and edit it as follows:

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

<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         bootstrap="web/core/tests/bootstrap.php"
         colors="true"
         beStrictAboutTestsThatDoNotTestAnything="true"
         beStrictAboutOutputDuringTests="true"
         beStrictAboutChangesToGlobalState="true"
         failOnWarning="true"
         printerClass="\Drupal\Tests\Listeners\HtmlOutputPrinter"
         cacheResult="false"
         xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
  <php>
    <ini name="error_reporting" value="32767"/> <!-- 32767 = E_ALL. -->
    <ini name="memory_limit" value="-1"/>
    <env name="SIMPLETEST_BASE_URL" value="http://my-project.ddev.site"/>
    <env name="SIMPLETEST_DB" value="mysql://db:db@db:3306/test"/>
    <env name="BROWSERTEST_OUTPUT_DIRECTORY" value=""/>
<!--    <env name="SYMFONY_DEPRECATIONS_HELPER" value="weak"/>-->
  </php>

  <testsuites>
    <testsuite name="unit">
      <directory>web/modules/custom/*/tests/src/Unit/</directory>
    </testsuite>
    <testsuite name="kernel">
      <directory>web/modules/custom/*/tests/src/Kernel/</directory>
    </testsuite>
    <testsuite name="functional">
      <directory>web/modules/custom/*/tests/src/Functional/</directory>
    </testsuite>
    <testsuite name="functional-javascript">
      <directory>web/modules/custom/*/tests/src/FunctionalJavascript/</directory>
    </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>

  <coverage>
    <include>
      <directory suffix=".php">web/modules/custom/*/src</directory>
    </include>
  </coverage>

</phpunit>

These are all the customizations that have been made:

1. Adjust the boostrap path to core’s bootstrap

bootstrap="web/core/tests/bootstrap.php"

2. Set site URL and database connection details (for Kernel, Functional… etc. tests). Note that PHPUnit will be executed inside the web container, so we must set “db” and port 3306 as our database host, as it is seen from the web container. We also set the testing database name to “test”, that is the convention.

<env name="SIMPLETEST_BASE_URL" value="https://my-project.ddev.site"/>
<env name="SIMPLETEST_DB" value="mysql://db:db@db:3306/test"/>

3. Finally, we adjust all test suites to only execute tests from our custom modules by default, if we execute phpunit without file parameters nor filters.

<testsuite name="unit">
  <directory>web/modules/custom/*/tests/src/Unit/</directory>
</testsuite>
<testsuite name="kernel">
  <directory>web/modules/custom/*/tests/src/Kernel/</directory>
</testsuite>
...

Installing PHPUnit

As simple as installing these packages with Composer

# ddev composer require --dev \
  phpunit/phpunit \
  phpspec/prophecy-phpunit \
  symfony/phpunit-bridge \
  dmore/chrome-mink-driver \
  behat/mink-browserkit-driver \
  behat/mink-selenium2-driver \
  mikey179/vfsstream
  • phpunit/phpunit – The PHPUnit core package.
  • phpspec/prophecy-phpunit – The Prophecy mocking system. In a lot of tests it is used instead of PHPUnit Mock Object Framework, the PHPUnit standard.
  • symfony/phpunit-bridge – Bridges the gap between Symfony and PHPUnit.
  • dmore/chrome-mink-driver – Required for some Kernel nad Functional tests.
  • behat/mink-browserkit-driver – Required for some Functional tests.
  • mikey179/vfsstream – Some tests make use of this virtual filesystem mocker.

Configuring DDEV Selenium Standalone Chrome

This config is necessary to execute Javascript and FunctionalJavascript Drupal tests. PHPUnit needs a headless Chromium browser to control and to send commands to. That’s what this DDEV addon provides.

Follow the instructions in https://github.com/ddev/ddev-selenium-standalone-chrome:

# ddev get ddev/ddev-selenium-standalone-chrome

Then, update your .ddev/config.selenium-standalone-chrome.yaml and set the local domain for your DDEV project, making sure you set it as http:// and not https:// (I got an “invalid cookie domain” error when I did).

#ddev-generated  <- REMOVE THIS LINE

...

- SIMPLETEST_BASE_URL=http://my-project.ddev.site

...

- DRUPAL_TEST_BASE_URL=http://my-project.ddev.site

...

- DTT_BASE_URL=http://my-project.ddev.site

Then, restart DDEV:

# ddev restart

Executing tests from the command line

If we want being able to execute our tests easily from outside the web container, we must create the file /.ddev/commands/web/phpunit with this content:

#!/bin/bash

phpunit "$@"

And now, outside the web container, we can do:

# ddev phpunit
# ddev phpunit --testsuite <name>

Executing tests from PHPStorm

From PHPStorm’s interface, we can go to any test case class and use the GUI to run its tests, either all at once or one by one, simply clicking on the run icons next to the class and test method definitions:

We can even right click on any directory in the project’s explorer and click on “Run (tests PHPUnit)” and all tests in that directory will be executed. This is very convenient when we want to debug only a test method or execute only one type of tests inside a module, for example.

Conclusion

Getting PHPStorm, DDEV, Drupal and PHPUnit to work together is not a trivial task. Although DDEV’s PHPStorm plugin eases a bit the task, we have yet to wire all the things correctly.