🇬🇧 Configure PHPUnit and test types in Drupal (Unit, Kernel, Browser)

✅ Setting Up PHPUnit in Drupal with DDEV
📄 File: phpunit.xml
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
bootstrap="core/tests/bootstrap.php"
colors="true"
verbose="true"
stopOnFailure="false"
printerClass="\Drupal\Tests\Listeners\HtmlOutputPrinter"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
<php>
<ini name="error_reporting" value="-1"/>
<env name="SIMPLETEST_DB" value="mysql://db:db@db/db"/>
<env name="SIMPLETEST_BASE_URL" value="http://drupal-test.ddev.site"/>
<env name="BROWSERTEST_OUTPUT_DIRECTORY" value="/var/www/html/web/sites/simpletest/browser_output" />
<env name="BROWSERTEST_OUTPUT_BASE_URL" value="http://drupal-test.ddev.site"/>
</php>
<testsuites>
<testsuite name="custom">
<directory>modules/custom</directory>
</testsuite>
</testsuites>
<listeners>
<listener class="\Drupal\Tests\Listeners\DrupalListener"/>
</listeners>
</phpunit>
🧪 To run PHPUnit:
ddev ssh
cd web
./vendor/bin/phpunit --testdox
To run a specific test:
./vendor/bin/phpunit -c phpunit.xml modules/custom/enrollment/tests/src/Unit/RoutingNoCacheOptionTest.php
Description:
./vendor/bin/phpunit :
- Run PHPUnit from the binary inside vendor/
- This is the version installed by Composer for the project (ensures compatibility with Drupal).
-c
--configuration
- You tell PHPUnit which configuration file (phpunit.xml) to use.
- Is it required?: Yes, if your file isn’t in the same directory where you run the command.
phpunit.xml
- XML file containing the configuration for PHPUnit
modules/custom/my_module/tests/Functional/HelloWorldTest.php
- Path to the test file you want to run.
- Only if you want to run a specific test. If you omit this, it will run all tests defined in phpunit.xml.
🧪 UnitTestCase
Info:
It’s a base class for tests that:
- Don’t load Drupal (neither the kernel nor the service container).
- Don’t access the database, files, or real services.
- Only test internal logic of small classes, in isolation.
- Use mocks or fakes when dependencies need to be simulated.
When to use UnitTestCase?
- Test pure functions (e.g., adding, formatting text)
- Validate methods that return strings or numbers
- Test classes that use mockable dependencies
- Verify the internal behavior of your logic
Example:
web/modules/custom/test_phpunit/src/EventSubscriber/EventsExampleSubscriber.php
<?php
namespace Drupal\test_phpunit\EventSubscriber;
/**
* Summary of EventsExampleSubscriber.
*/
class EventsExampleSubscriber {
/**
* @param $string String
* Simple function to check string.
*/
public function checkString($string) {
return $string ? TRUE : FALSE;
}
}
web/modules/custom/test_phpunit/test_phpunit.services.yml
services:
test_phpunit_subscriber:
class: Drupal\test_phpunit\EventSubscriber\EventsExampleSubscriber
web/modules/custom/test_phpunit/tests/src/Unit/EventsExampleUnitTest.php
<?php
namespace Drupal\Tests\test_phpunit\Unit;
use Drupal\Tests\UnitTestCase;
use Drupal\test_phpunit\EventSubscriber\EventsExampleSubscriber;
/**
* Test test_phpunit EventsExampleSubscriber functionality.
*
* @group test_phpunit
*
* @ingroup test_phpunit
*/
class EventsExampleUnitTest extends UnitTestCase {
/**
* test_phpunit EventsExampleSubscriber object.
*
* @var-object
*/
protected $eventExampleSubscriber;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
$this->eventExampleSubscriber = new EventsExampleSubscriber();
parent::setUp();
}
/**
* Test simple function that returns true if string is present.
*/
public function testHasString() {
$this->assertTrue($this->eventExampleSubscriber->checkString('Some String'));
}
}
./vendor/bin/phpunit modules/custom/test_phpunit/tests/src/Unit/EventsExampleUnitTest.php
Summary:
- Test a class called EventsExampleSubscriber.
- Verify that the checkString() method works correctly.
⚙️ KernelTestBase
KernelTestBase is a base class for writing integration tests in Drupal. It falls somewhere between a UnitTestCase (very lightweight) and a BrowserTestBase (very comprehensive), and is designed to test Drupal logic that requires the loaded service container, but not a full user interface.
When to use KernelTestBase?
When you need to test code that:
- Uses real Drupal services (config.factory, entity_type.manager, etc).
- Depends on enabled modules.
- Uses entities (node, user, config, etc.).
- Uses events or listeners.
- Manipulates persistent data (configuration, content).
- Involves database records (but not UI navigation).
Example:
web/modules/custom/test_phpunit/tests/src/Kernel/EventsExampleServiceTest.php
<?php
namespace Drupal\Tests\test_phpunit\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\test_phpunit\EventSubscriber\EventsExampleSubscriber;
/**
* Test to ensure 'test_phpunit_subscriber' service is reachable.
*
* @group test_phpunit
*
* @ingroup test_phpunit
*/
class EventsExampleServiceTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['test_phpunit'];
/**
* Test for existence of 'test_phpunit_subscriber' service.
*
* Que el servicio test_phpunit_subscriber está registrado en el services.yml.
* Que apunta a la clase correcta.
* Que el módulo test_phpunit lo proporciona correctamente.
*/
public function testEventsExampleService() {
$subscriber = $this->container->get('test_phpunit_subscriber');
$this->assertInstanceOf(EventsExampleSubscriber::class, $subscriber);
}
}
./vendor/bin/phpunit modules/custom/test_phpunit/tests/src/Kernel/EventsExampleServiceTest.php
Summary:
- Obtains the test_phpunit_subscriber service from the Drupal container.
- Verifies that the received instance is of type EventsExampleSubscriber.
- If everything is correct, the test passes. If the service is not registered correctly, or has a different type, it fails.
🌐 BrowserTestBase
BrowserTestBase is the base class in Drupal for running full-featured functional tests that simulate a browser. It’s used when you want to test how a Drupal website behaves from the user’s perspective.
When to use BrowserTestBase?
- Access a route and verify the HTML
- Test that a form displays and responds
- Verify access permissions (403, 404)
- Simulate an anonymous or authenticated user
Example:
web/modules/custom/test_phpunit/tests/src/Functional/HelloWorldTest.php
<?php
namespace Drupal\Tests\my_module\Functional;
use Drupal\Tests\BrowserTestBase;
/**
* Verifies that the homepage loads successfully.
*
* @group my_module
*/
class HelloWorldTest extends BrowserTestBase {
/**
* The default theme used for the test.
*/
protected $defaultTheme = 'stark';
/**
* Modules required to run this test.
*/
protected static $modules = ['node'];
/**
* Check that the home page returns a 200 response.
*/
public function testHomePageLoads() {
$this->drupalGet('<front>');
$this->assertSession()->statusCodeEquals(200);
}
}
./vendor/bin/phpunit modules/custom/test_phpunit/tests/src/Functional/HelloWorldTest.php
Summary:
- Simulates the browser accessing the site’s home page (/).
- Verify that the server response was HTTP 200 (OK).
Author by Eduardo Telaya.