HorusKol

Running tests and debugging containerized apps in PhpStorm

July 29, 2021

A pair of old sailing ships struggling in stormy waters.

Image source: Wikimedia Commons

Recently I figured I'd give Laravel Sail a whirl, since it's now the recommended way to spin up a new development project.

Sail is a command-line tool that helps you set up a Docker-based containerized development environment, along with some helpers to work with the various containers.

In order to take control of your container setups, you should publish Sail's Dockerfiles:

php artisan sail:publish

If you are creating a fresh Laravel application with Sail, I recommend publishing the Dockerfiles and modifying before starting the containers with sail up, otherwise you might have to go through a couple of length build processes.

Publishing the Dockerfiles will create a couple of different configurations in a docker directory, and update the project's docker-composer.yml file to point to them instead of the original vendor files.

If you've already started using Sail or Docker, then make sure to shut down any containers your project is running either with a sail down -v or docker-compose down -v.

Xdebug configuration for your Docker containers

First up, you should have an APP_DEBUG=true entry in you local .env file.

Then, edit your docker-compose.yml so that you pass this value as an argument into PHP container you're spinning up to run the app locally:

services:
    laravel.test:
        build:
            context: ./docker/8.0
            dockerfile: Dockerfile
            args:
                WWWGROUP: '${WWWGROUP}'
                XDEBUG: ${APP_DEBUG}
        image: sail-8.0/app

Finally, in the Dockerfile, we need to make a couple of changes.

Edit the firstline in the Dockerfile to name the build/image:

FROM ubuntu:21.04 AS php-base-8

And then add the following to the bottom of the file:

FROM php-base-8

# Import the XDEBUG flag
ARG XDEBUG

# Install Xdebug if required
RUN if ${XDEBUG} ; then \
    apt-get update \
    && apt-get install -y php8.0-xdebug \
    && apt-get -y autoremove \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*; \
fi;

This creates a multistage build which avoids having to rebuild the original image just to load/unload Xdebug.

You can confirm that Xdebug is installed by spinning up the container after building it and checking the php version running in the container.

sail build
sail up -d
sail php -v

PHP 8.0.8 (cli) (built: Jul  1 2021 15:27:21) ( NTS )
Copyright (c) The PHP Group
Zend Engine v4.0.8, Copyright (c) Zend Technologies
    with Zend OPcache v8.0.8, Copyright (c), by Zend Technologies
    with Xdebug v3.0.4, Copyright (c) 2002-2021, by Derick Rethans

php.ini

I've seen some tutorials also add Xdebug configuration into the php.ini file that is copied into the container, but this isn't necessary if you're working with PhpStorm, as it will inject the necessary configuration.

PhpStorm

Open PhpStorm's settings and open the PHP section.

The PHP section of PHPStorm Settings.

Select the PHP version you want to write your application in, and click on the '...' button next to the CLI Interpreters drop-down selector.

The CLI interpreter dialog of PHPStorm Settings.

If you don't already have a laravel.test interpreter in the list, create a new one and configure it as follows:

  • Name: laravel.test
  • Server: Docker
  • Configuration file: ./docker-compose.yml
  • Service: laravel.test
  • Lifecycle: Always start a new container ('docker-compose run')
  • PHP Executable: php

Apply these changes, and then open the Run menu in the IDE. From here, select the Edit Configurations menu command.

PHPStorm runtime configurations dialog.

Click on the Edit Configuration Templates link in the bottom left of the dialog, and open the PHPUnit template:

PHPStorm configuration for PHPUnit.

Change the command line interpreter to the laravel.test interpreter we created and apply the changes.

Now, whenever you run tests they will use a new Docker instance using the configuration we created above, and you should see output like this:

[docker-compose://[/var/checkouts/websites/dunnit/docker-compose.yml]:laravel.test/]:php /var/www/html/vendor/phpunit/phpunit/phpunit --configuration /var/www/html/phpunit.xml --teamcity
Testing started at 6:10 pm ...
WARNING: The WWWGROUP variable is not set. Defaulting to a blank string.
WARNING: The WWWUSER variable is not set. Defaulting to a blank string.
Creating volume "dunnit_phpstorm_helpers_PS-211.7628.25" with default driver
Starting dunnit_meilisearch_1 ... 
Starting dunnit_selenium_1    ... 
Starting dunnit_redis_1       ... 
Starting dunnit_mysql_1       ... 
Starting dunnit_mysql_1       ... donePHPUnit 9.5.7 by Sebastian Bergmann and contributors.

Expected status code [200] but received 404.
Failed asserting that 200 is identical to 404.
 /var/www/html/vendor/laravel/framework/src/Illuminate/Testing/TestResponse.php:177
 /var/www/html/tests/Feature/ExampleTest.php:19

Expected status code [401] but received 404.
Failed asserting that 401 is identical to 404.
 /var/www/html/vendor/laravel/framework/src/Illuminate/Testing/TestResponse.php:177
 /var/www/html/tests/Feature/Todo/AddTodoTest.php:19

Time: 00:02.147, Memory: 30.00 MB

FAILURES!
Tests: 3, Assertions: 3, Failures: 2.
Process finished with exit code 1

Additionally, when you run debug sessions, they should run against the relevant instance (either the current application or PHPUnit test instance), allowing you to work with breakpoints and navigate the stacktrace as you would with a local development.