Testing a Drupal 9 Module
Introduction
It has been difficult and painful, yet I have very good reasons to write test-driven drupal. For years I've been saying that testing is superior to strong types (the same argument they use to promote typescript, should be used to promote TDD instead, and testing is a far superior mindset). To be true to my word I employ test-driven development (TDD) in every stack: ruby, front-end javascript, php and finally python. While somewhat difficult, Drupal developers have been writing tests for a while, and use advanced integration tests. In this article, I will talk about setting up the testing environment and running a single kernel test.
I recently upgraded from Drupal 9 to Drupal 10 and had to change versions of everything. I generally don't recommend it: I recommend comfortably staying on the old version. Drupal 10 is fatter and slower than Drupal 9, and I actually *lost* functionality after upgrade, and didn't gain any new functionality. I therefore stopped the migration of my ecosystem and will maintain the non-migrated drupal sites on version 9, while I can. I will maintain the drupal 10 sites as well without reverting - because reverting would require me to do work to go backwards - something I cannot recommend.
I guess the main motivation and words of encouragement I have for beginner/intermediate drupal developers who're looking to incorporate testing is, that it works. If you feel frustrated with all the moving parts and somewhat counterintuitive setup, I want to say, have faith that you can make it work, issues can be resolved, and it does begin to work after a while.
And now for the technical part.
Resolving Dependencies
I got stuck with multiple version mismatches! The error that prints is some obscure, unsearchable error, and to fix it I had to version-match multiple packages. You *have to* specify versions, it seems that composer will not do a good job for you. The dependency tree is not obvious: version 10.6 *requires* version 7.4 elsewhere. I don't know your exact versions, and you may ask GPT or otherwise make sure your versions are right. I had to also downgrade two unrelated modules: "symfony/css-selector": "^6.4" and "symfony/dom-crawler": "^6.4".
composer require --dev drupal/core-dev:10.6.12 -W
composer require --dev symfony/phpunit-bridge:^7.3 ## drupal 10.6
composer require --dev phpunit/phpunit
composer require --dev behat/minkEventually, my versions worked.
I then asked GPT to write a minimal test. We'll use that one instead of my actual tests.
<?php
// file $MODULE_ROOT/tests/src/Kernel/SanityTest.php
namespace Drupal\Tests\ish_drupal_module\Kernel;
use Drupal\KernelTests\KernelTestBase;
class SanityTest extends KernelTestBase {
protected static $modules = [ 'system' ];
public function testTrue(): void {
$this->assertTrue(TRUE);
}
}And now let's try to run this test.
Wiring the Testing App
Now, an interesting and important question is to how to configure the testing environment for the module. I deploy the module with composer inside docker, so production deployment includes (1) bumping the version in composer.json and (2) running `composer require wasya-co/ish_drupal_module`. This obviously is not a test environment, and so a good test harness has to be added. I decided to adapt a strategy from the ruby-on-rails ecosystem. I package a minimal drupal app, via docker, inside the module - even though the module is expected to live inside some other production environment. This way, the module carries its own app, for testing only, which makes it self-sufficient for testing, and does not depend on another project.
You can see the actual wiring of my module because it is open-source: https://github.com/wasya-co/ish_drupal_module
I copy-pasted the docker-compose.yml file from production. The important change is to not use volumes, and use bind volumes instead.
It doesn't have to be a functional app, since I only use it for testing. If you would like me to make it a functioning app, please drop me a message.
Once I start the container, I can login:
./scripts/login testAnd run a test in the container:
composer install
export SIMPLETEST_DB="mysql://root:test1234@mysql/ish_drupal_module_test"
export SIMPLETEST_BASE_URL="http://127.0.0.1"
./vendor/bin/phpunit \
-c /var/www/html/web/core/phpunit.xml.dist \
web/modules/custom/ish_drupal_module/tests/src/Kernel/SanityTest.phpAgain, notice that the test is encapsulated inside the module, as it should be. No external code is required to run it.
And voila! We are in business.
