This README provides a comprehensive guide for setting up automated testing of Drupal modules using GitHub Actions.
This setup allows you to run PHPUnit tests for your Drupal custom module in a GitHub Actions workflow. The workflow:
- Sets up a PHP environment
- Installs Drupal with a MySQL database
- Enables your custom module and its dependencies
- Starts a web server
- Runs your PHPUnit tests
- PHP 8.0+ with required extensions (mbstring, pdo, xml, dom, json)
- MySQL 5.7+
- Composer
- Drush
- Docker Desktop with WSL2 backend (for Windows users)
- act for local GitHub Actions testing
- Selenium server for JavaScript tests
- A GitHub repository with your Drupal module
- A properly configured
.github/workflows/phpunit.ymlfile
-
Install Docker Desktop with WSL2 backend:
# Run PowerShell as Administrator choco install docker-desktop -
Install act for local GitHub Actions testing:
# Run PowerShell as Administrator choco install act-cli
-
Install Docker:
# Ubuntu sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io # macOS brew install --cask docker -
Install act:
# Ubuntu curl -s https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash # macOS brew install act
To run ALL tests using GitHub Actions workflow locally:
-
Start Docker Desktop as Administrator
-
Run this command in PowerShell as Administrator:
act -W .github/workflows/phpunit.yml -j phpunit -P ubuntu:ghcr.io/catthehacker/ubuntu:act-latest --container-daemon-socket "npipe:////./pipe/docker_engine"-
Run act:
act -W .github/workflows/phpunit.yml
These files should be placed in your project's root directory following standard Drupal structure:
.github/workflows/phpunit.yml- GitHub Actions workflow filephpunit.xml- PHPUnit configurationweb/modules/custom/MODULE_NAME- Your custom module with:tests/src/Unit/- Unit teststests/src/Kernel/- Kernel teststests/src/Functional/- Functional teststests/src/FunctionalJavascript/- Browser tests
name: PHPUnit Tests
on:
push:
branches:
- main
- develop
pull_request:
jobs:
phpunit:
runs-on: ubuntu-latest
services:
mysql:
image: mysql:5.7
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: drupal_testing
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping"
--health-interval=10s
--health-timeout=5s
--health-retries=5
selenium:
image: selenium/standalone-chrome:latest
ports:
- 4444:4444
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.2
extensions: gd, pdo_mysql, mbstring
coverage: none
- name: Install Composer dependencies
run: |
composer install --no-interaction --prefer-dist
cp web/sites/default/default.settings.php web/sites/default/settings.php
composer require drush/drush --no-interaction
- name: Install Drupal
env:
SIMPLETEST_DB: "mysql://root:root@127.0.0.1/drupal_testing"
SIMPLETEST_BASE_URL: "http://127.0.0.1"
run: |
# Use Drupal's test site install script (adjust the path if needed)
php web/core/scripts/test-site.php install --db-url="$SIMPLETEST_DB" --base-url="$SIMPLETEST_BASE_URL" --install-profile=standard --langcode=en
- name: Enable required modules
run: |
chmod +x ./vendor/bin/drush
chmod +x ./vendor/drush/drush/drush
chmod +x ./vendor/bin/drush.php
# Enable module with debug output
php ./vendor/drush/drush/drush en draggable_mapper -y
- name: Prepare testing environment
run: |
# Create all parent directories with proper permissions
mkdir -p web/sites/simpletest/browser_output
# Set world-writable permissions recursively
chmod -R 777 web/sites
- name: Run PHPUnit
env:
BROWSERTEST_SELENIUM_URL: "http://localhost:4444"
MINK_DRIVER_ARGS_WEBDRIVER: '["chrome", {"browserName": "chrome", "platformName": "LINUX", "goog:chromeOptions": {"args": ["--disable-gpu", "--headless", "--no-sandbox", "--disable-dev-shm-usage"]}}, "http://localhost:4444"]'
run: |
# Start PHP's built-in web server in the background
cd web
php -S 0.0.0.0:8080 .ht.router.php &
SERVER_PID=$!
echo "Started PHP server with PID: $SERVER_PID"
# Wait for server to be ready
sleep 3
# Test connection
curl -I http://127.0.0.1:8080 || echo "Web server not responding"
# Return to root directory before running tests
cd ..
# Run tests
export BROWSERTEST_OUTPUT_DIRECTORY="$(pwd)/web/sites/simpletest/browser_output"
chmod +x ./vendor/bin/phpunit
./vendor/bin/phpunit -c $(pwd)/phpunit.xml --verbose --debug --log-junit test-results.xml<?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"/>
<ini name="memory_limit" value="-1"/>
<env name="SIMPLETEST_BASE_URL" value="http://127.0.0.1:8080"/>
<env name="SIMPLETEST_DB" value="mysql://root:root@127.0.0.1/drupal_testing"/>
<env name="BROWSERTEST_OUTPUT_DIRECTORY" value="web/sites/simpletest/browser_output"/>
<env name="BROWSERTEST_SELENIUM_URL" value="http://localhost:4444"/>
// minK CLASS??
<env name="SYMFONY_DEPRECATIONS_HELPER" value="weak"/>
<env name="PHPUNIT_ASSERTIONS_DISABLE" value="assertionError"/>
<env name="XDEBUG_MODE" value="off"/>
</php>
<testsuites>
<testsuite name="unit">
<directory>web/modules/custom/draggable_mapper/tests/src/Unit</directory>
</testsuite>
<testsuite name="kernel">
<directory>web/modules/custom/draggable_mapper/tests/src/Kernel</directory>
</testsuite>
<testsuite name="functional">
<directory>web/modules/custom/draggable_mapper/tests/src/Functional</directory>
</testsuite>
<testsuite name="functional-javascript">
<directory>web/modules/custom/draggable_mapper/tests/src/FunctionalJavascript</directory>
</testsuite>
</testsuites>
<listeners>
<listener class="\Drupal\Tests\Listeners\DrupalListener">
</listener>
</listeners>
</phpunit>Your module should follow the standard Drupal module structure, with tests in the appropriate directories:
MODULE_NAME/
├── MODULE_NAME.info.yml
├── MODULE_NAME.module
├── src/
│ └── ...
└── tests/
└── src/
├── Functional/
│ └── ModuleNameFunctionalTest.php
├── FunctionalJavascript/
│ └── ModuleNameJsTest.php
├── Kernel/
│ └── ModuleNameKernelTest.php
└── Unit/
└── ModuleNameUnitTest.php
This GitHub Actions workflow enables continuous integration by:
- Automated Testing: Running tests automatically on every push and pull request
- Quality Assurance: Ensuring your module works correctly before merging changes
- Early Bug Detection: Identifying issues quickly in the development cycle
To enhance your CI pipeline:
- Add code linting (PHP_CodeSniffer, ESLint)
- Add static analysis (PHPStan, Psalm)
- Add code coverage reports (with tools like Codecov)
Extend this workflow for continuous deployment by:
- Add a deployment step that triggers after successful tests on the main branch:
# Add to your GitHub workflow file
- name: Deploy to development
if: github.ref == 'refs/heads/main' && success()
run: |
# Add deployment scripts here
./scripts/deploy-to-dev.sh- Create deployment environments for different stages:
name: Drupal Module CI/CD
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
jobs:
test:
# Your testing job
deploy-dev:
needs: test
if: github.ref == 'refs/heads/develop' && success()
environment: development
runs-on: ubuntu-latest
steps:
# Deployment steps
deploy-prod:
needs: test
if: github.ref == 'refs/heads/main' && success()
environment: production
runs-on: ubuntu-latest
steps:
# Production deployment steps-
Web Server Connection Issues:
- Ensure the PHP server is running on 0.0.0.0 (all interfaces), not just localhost
- Make sure the port (8080) is available
- Check that SIMPLETEST_BASE_URL matches the server URL
-
MySQL Connection Issues:
- Verify the database credentials match between Drupal installation and PHPUnit config
- Check that the MySQL service is healthy
-
Module Not Found:
- Ensure the module is correctly placed in web/modules/custom/
- Verify the module name in Drush commands matches your module's machine name
-
Windows-specific Issues:
- Always run Docker Desktop as Administrator
- Run PowerShell as Administrator when executing act commands
- Use the correct Docker socket path:
npipe:////./pipe/docker_engine
