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

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.


© 2024. All rights reserved.