Debug any of Drupal's PHPUnit tests in PhpStorm with a DDEV-Local Environment

I'm a big fan of both PhpStorm and DDEV-Local for Drupal development work. Both are very powerful tools, and I'm sure I only scratch the surface of what they're capable of. I'm always learning new tricks that make PHP development a more enjoyable experience. The latest: configuring PhpStorm to allow me to run Drupal module tests in DDEV using the PhpStorm UI instead of SSHing into the DDEV web container and running them via the CLI.

I learned how to do this by following the steps in this excellent post by @sasunegomo, and then combining that with this blog post by @nmdmatt and our own series on Automated Testing in Drupal 8.

The end result? I can now run and debug tests in the same environment where the code is being developed while using the handy tools provided by PhpStorm.

Example:

This setup requires a couple of steps:

  1. Add a Chromedriver docker image to DDEV to run headless Chrome for FunctionalJavascript tests
  2. Create a docker-compose file based on your DDEV configuration that PhpStorm can understand
  3. Configure PhpStorm to use the docker-compose provided PHP binary when executing PHP commands
  4. Configure PhpStorm to use the new remote interpreter for Composer and PHPUnit

Add a ChromeDriver service to DDEV

If you want to run any of Drupal's FunctionalJavascript tests you'll need to have ChromeDriver (or some other WebDriver API implementation) running. The easiest way to do this is to add a ChromeDriver service to DDEV by using the same drupalci/chromedriver:production Docker image that the Drupal.org test bot uses. Next, provide some configuration in the form of environment variables so PHPUnit/Drupal can use it.

Create the file .ddev/docker-compose.chromedriver.yaml with the following content in your DDEV enabled project:

version: '3.6'
services:
  chromedriver:
    container_name: ddev-${DDEV_SITENAME}-chromedriver
    # Use the Docker image that the Drupal.org test bot uses.
    image: drupalci/chromedriver:production
    labels:
      # Make sure ddev can discover the service.
      com.ddev.site-name: ${DDEV_SITENAME}
      com.ddev.approot: $DDEV_APPROOT
      com.ddev.app-url: $DDEV_URL
  # This links the Chromedriver service to the web service defined
  # in the main docker-compose.yml, allowing applications running
  # in the web service to access the driver at `chromedriver`.
  web:
    volumes:
      # If you run tests in PhpStorm with code coverage reports this
      # is where they are stored.
      - ../coverage:/opt/phpstorm-coverage
    links:
      - chromedriver:$DDEV_HOSTNAME
    environment:
      # PHPUnit configuration could also be done via phpunit.xml.
      SYMFONY_DEPRECATIONS_HELPER: weak
      SIMPLETEST_DB: mysql://db:[email protected]:3306/db
      SIMPLETEST_BASE_URL: http://web
      BROWSERTEST_OUTPUT_DIRECTORY: /var/www/html/private/browsertest_output
      BROWSERTEST_OUTPUT_BASE_URL: $DDEV_PRIMARY_URL
      MINK_DRIVER_ARGS_WEBDRIVER: '["chrome", {"browserName":"chrome","chromeOptions":{"args":["--disable-gpu","--headless", "--no-sandbox"]}}, "http://chromedriver:9515"]'

Then start, or restart, DDEV:

ddev start

DDEV will see, and use, the file you created above and create a new container with ChromeDriver installed. It will also add some necessary environment variables and other configuration to the default web container.

Create a docker-compose.yml file for PhpStorm

It's possible to configure PhpStorm to use a variety of different PHP CLI interpreters. That is, you can tell PhpStorm what PHP binary you want it to use when running commands. Normally it'll use the one(s) it can find in your $PATH. But it's also possible to execute the commands using the PHP Interpreter that's installed in a Docker container.

To do that, PhpStorm reads your docker-compose.yml file and uses the information it contains to connect to the appropriate container and run the commands in that container using docker-compose exec. DDEV also uses Docker. Yay! However, DDEV ads some additional scripting around docker-compose and does a few things that PhpStorm can not directly understand. So we can't use DDEV's docker-compose file. But we can create a new one.

Using the ddev debug command you can dump the compiled docker-compose configuration that is used for your environment into a standard docker-compose.yml file that PhpStorm can understand.

In the root directory of your DDEV/Drupal project run these commands:

mkdir build;
ddev debug compose-config > build/docker-compose.yml

Fixes required for PhpStorm ≤ 2021.1

There's a bug in versions of PhpStorm ≤ 2021.1 (it's already fixed, so if you're reading this when there's a new version of PhpStorm you probably won't need this step) that prevents it from being able to parse the docker-compose.yml file you just created because that file contains some ports configuration for the web container. Luckily, we can just remove it and it'll work fine.

Open the build/docker-compose.yml file, find the lines that look like the following in the configuration for the web container and remove them:

ports:
    - 127.0.0.1::80/tcp
    - 127.0.0.1::443/tcp

Pro-tip: If you're recreating this file a lot you can create a custom DDEV command that will export the docker-compose.yml file and delete these lines all in one go. See this blog post for more details.

Use the docker-compose PHP Interpreter

Next, configure PhpStorm to use the PHP Interpreter inside the DDEV provided web container.

In PHP Storm navigate to Preferences > PHP and add a new PHP CLI Interpreter by clicking the button next to the dropdown where you choose which CLI Interpreter to use.

PhpStorm preferences dialog with arrow pointing to the "..." button where you can add a new interpreter

In the resulting pop-up click the "+" button and choose the From Docker, Vagrant ... Remote ... option.

  • Choose the Docker Compose option at the top
  • Set Configuration files: to the build/docker-compose.yml file you created earlier
  • Choose web from the Service: dropdown
  • Set the Environment variable COMPOSE_PROJECT_NAME=ddev-{drupal-project} where {drupal-project} is the name of your DDEV project. (Hint: use ddev describe if you don't know)
  • Then click Add to save your new configuration
  • On the resulting screen in the Lifecycle section change to Connect to existing container
  • Optionally, change the Name to something more descriptive than web like ddev-web.
  • And finally click OK

You should now have a new PHP CLI Interpreter named ddev-web similar to the one shown below:

PhpStorm preferences dialog with PHP CLI interpreter settings from above added

Curious about that [COMPOSE_PROJECT_NAME variable](https://docs.docker.com/compose/reference/envvars/#compose_project_name)? We need to set that so that PhpStorm uses the correct container names when running commands like docker-compose exec. DDEV names containers like ddev-d9-web and ddev-d9-db. This configuration accounts for that.

Use the new CLI Interpreter for Composer and PHPUnit

Next we need to tell PhpStorm to use the CLI Interpreter we just configured when running both Composer and PHPUnit commands.

In the PhpStorm preferences, go to PHP > Composer and:

  • Under Execution choose the Remote Interpreter option
  • Choose the new interpreter configured above in the CLI Interpreter dropdown
  • Click Apply to save your changes

PhpStorm preferences dialog with red box highlighting the section that contains configuration for the Composer remote interpreter

Then, in the PhpStorm preferences go to PHP > Test Frameworks and:

  • Click the "+ button to add a new configuration and choose the PHPUnit by Remote Interpreter option
  • Choose the interpreter you configured earlier from the list in the dropdown and click Ok
  • Under Test Runner set the path to your PHPUnit configuration file, e.g. /var/www/html/core/phpunit.xml.dist. Note: This is the path inside the DDEV web container!

PhpStorm preferences dialog showing configuration for phpunit/test framework to use a remote interpreter as described above.

Run your PHPUnit tests using the PhpStorm UI

Open a file containing a test, or set of tests you want to run. And then use the PHPStorm UI to run them. When you do, PhpStorm should execute the relevant phpunit command inside of the ddev-web container using docker-compose exec.

Example:

PhpStorm IDE showing results of running a single phpunit test in the UI.

Want to debug a test using Xdebug? No problem!

  • Run the command ddev xdebug on to enable Xdebug for the current project.
  • Open the file you want to debug and set a breakpoint
  • Run the test using the PhpStorm UI selecting the Debug instead of the Run option
  • PhpStorm should execute your tests using phpunit inside the ddev-web container and halt execution at your breakpoint

Example:

PhpStorm IDE showing a breakpoint set in a file and phpunit halting at that breakpoint for debugging.

Conclusion

PhpStorm has some features in its UI that can make it easier to execute, and debug, PHPUnit tests. For example, you can choose an individual test method to debug right from the editor and not have to memorize the complex CLI commands. With a bit of configuration, it's possible to have PhpStorm execute those commands within a DDEV-Local environment -- a setup that is in many cases easier to get started with than installing and configuring all the various tools required independently.

Related Topics: 

Comments

Add new comment