Automated tests
The PHPUnit testing framework is used by symfonic Drupal. This chapter provides an introduction to making use of GitLab's CI testing framework for PHPUnit tests, and how to debug tests.
Table of contents
- Introduction
- Setting up to use GitLab CI
- The PHPUnit framework
- Testing a simple UnitTestCase
- Running a single functional test
- Running a single kernel test
- Running all tests
- Assertions
- Debugging tests
- Converting legacy tests to PHPUnit tests
- Troubleshooting
- Final word
Introduction
[This was previously about running tests locally. This has been deprecated in favout of GitLab TI tests. This needs a a major rewrite.]
DO: GitLab CISetting up to use the GitLab CI
To set up a project for GitLab CI testing, all you need to do it to
copy .gitlab-ci.yml
to the root of your project and add
it to Git. (No changes are necessary.) When you push to the repo,
automated tests are enabled. There are three stages:
- Build: Use Composer to build the project
- Validate: PHPCS, using four different validators.
- Test: The actual PHPUnit tests
Even if there are no PHPUnit tests created, you will get feedback from the first two stages.
In the Notify module project, there is a minimal
functional test named LoadTest.php
. It can be used as a
bare bones PHPUnit test. Just remember to replace the three instances
of "notify" with the short name of your project.
The PHPUnit framework
Drupal comes with automated tests that help prevent regressions and promoting code quality.
The PHPUnit core testing framework is used by Symfony-based Drupal supports the following types of PHP-based tests:
- Functional: Provides a complete clean install of Drupal for each test. The Drupal installation needs to be reachable via the web server. Functional tests are also called browser tests. These tests are very similar to what is provided by the legacy SimpleTest framework.
- Unit: Provides simple tests of individual units. Intended to test the smallest parts of an application. These are used to test the parts of your code that can run without Drupal and without a database connection. This means that you can test individual functions, methods, and some classes.
- Kernel: Provides integration tests with the kernel, without requiring a UI. Requires a working database connection. This may be used to test using hooks, services, plugins, and anything API-side within Drupal.
Tests based upon PHPUnit are created extending one of the following classes:
- BrowserTestBase (functional web tests)
- UnitTestCase
- KernelTestBase
- WebDriverTestBase (javascript enabled web tests using WebDriver - how do these fit in?)
Sources: PHPUnit: PHPUnit Manual, Drupal.org: PHPUnit in Drupal, Atlantic.bt: A Full Guide to PHPUnit in Drupal 8, Drupal.org: Types of tests.
Socketwrench: Writing Automated Tests in Drupal 8:
The BrowserTestBase should be used for most tests. On every test method you have, the whole of Drupal will be reset, all necessary modules will be enabled, the cache cleared and rebuilt. This is a slow and complex process, so each test will take some time.
Drupal considers anything that doesn't do anything with Drupal to be a unit test. So if you are testing something that doesn't need to access the database, entities or output, you can use DrupalUnitTestCase, which is faster and more lightweight.
In addtition, for JS there are these frameworks:
- Nightwatch.js (recommended)
- Functional JS (deprecated)
They are not yet described by me.
Invariants
The Drupal test framework is based upon invariants. In programming, an invariant is a condition that must hold everytime the program executes through a specific position.
The Drupal test framework perform invariant checking by means of assertions. I.e. you assert that a certain invariant exists in a specific condition at a certain point in the program's execution.
Note that Drupal's automated testing does not actually tell you whether your code is corret or not. All it does it to let you specify invariants and check whether they hold.
Testing a simple UnitTestCase
In this example, we shall create a very simple PHPUnit test for the custom Hello World extension introduced in a previous chapter.
It should be said that the test used in this demostrator is not
meaningful. It is used there to demonstrate the framework. The unit
test is not going to interact with Drupal. All it will do is
to check that the result of summing two numbers is what one should
expect. The test will not interact with Drupal, so it will be written
as a subclass of the “UnitTestCase
” class.
Start out by creating folder named “tests
” in the
module's root folder, and inside it a folder named “src
”.
Since this is a unit test, there should be a subdirectory named
“Unit
”. The PHP file containing need to have a name
ending with “Test
”. Here is the path from the module's root folder:
“tests/src/Unit/HelloWorldTest.php
”. Its contents may be
this:
<?php namespace Drupal\Tests\hello_world\Unit; use Drupal\Tests\UnitTestCase; /** * Our main test class for hello_world. */ class HelloWorldTest extends UnitTestCase { public function testAddTwoNumbers() { $result = 2 + 2; $this->assertEquals($result, 4); } }
To run this specific unit test, use:
$ vendor/bin/phpunit web/modules/custom/hello_world/tests/src/Unit/HelloWorldTest.php PHPUnit 9.5.21 by Sebastian Bergmann and contributors. Testing Drupal\Tests\hello_world\Unit\HelloWorldTest . 1 / 1 (100%) Time: 291 ms, Memory: 6.00MB OK (1 test, 1 assertion)
If an assertion fails, the following will appear:
FAILURES! Tests: 1, Assertions: 1, Failures: 1.
Running a single functional test
Functional tests interact with the website through browser that is part of the testing framework. For this reason, functional tests have to be invoked with a user that is the web server user. You can either configure Apache to run as your own system user or run tests as the web server user. The command below let you do the latter. Source: Drupal.org: Permission problems.
To become the web-server user:
$ sudo -u www-data bash
If you get this warning: “User warning: mkdir(): Permission Denied”, you are not running the tests as the web server user.
To run functional tests you extend the BrowserTestBase
class. If the test produces errors, you may want to capture these in
a text file named phpunit.txt
:
$ vendor/bin/phpunit \ web/modules/custom/hello_world/tests/src/Functional/HelloWorldFunctionalTest.php \ > phpunit.txt
To view the putput in the browser, convert it to an HTML-report with this command:
$ phpunitreport.pl
This produces this file in the siteroot: phpunit.html
.
You may set up a shortcut to it in the admin toolbar.
To delete phpunit.txt
in the siteroot directory, use:
$ phpunitclean.sh
Running a single kernel test
To run kernel tests you extend either
the KernelTestBase
or
the EntityKernelTestBase
class./p>
Running all tests
PHPUnit is able to locate and run tests under the directory given as argument. Use the first line to run all tests that exists for custom extensions. Use the second line to run ll the tests that exist for the contributed extension Notify.
$ vendor/bin/phpunit web/modules/custom $ vendor/bin/phpunit web/modules/constrib/notify
Assertions
https://phpunit.readthedocs.io/en/9.5/assertions.htmlassertCount()
assertCount($expectedCount, $haystack[, string $message = ''])
The $haystack
is something countable, typically an array.
Reports an error identified by $message
if the number of elements in is not what is expected.
Debugging tests
Recent versions of PHPUnit swallow output, and the various Devel functions to dump variables doe not work.
There is a DSE question: How do I dump variables to the screen when running PHPUnit tests, that provides some ideas about how to get around this.
For example, if you're extending BrowserTestBase, you may add a message to the assertion:
$this->assertCount(1, $this->getMails(), 'One email is sent.', 'Bother!');
It
has also been suggested that you may use the --debug
flag. In my experience has been that the --debug
flag
provides a bit more information about when tests start and stop, but
not a variable dump. The message parameter produces no screen output,
unless your test class is extending BrowserTestBase.
You may create your own logging function. Place this in the file containing the tests, before the test class:
function mylog($var, $label = NULL) { $tmp_file = '/tmp/my_debug.log'; $output = ''; if(!is_null($label)) { $output = $label . ': '; } $output .= print_r($var, 1) . PHP_EOL; file_put_contents($tmp_file, $output, FILE_APPEND); }
I need to explore whether I can use this technique to look at the current page for debugging:
$pagecontent = $this->drupalGetContent(); debug($pagecontent);
Converting legacy tests to PHPUnit tests
Source: Drupal.org: Converting SimpleTests to PHPUnit tests.
Troubleshooting
Drupal\Core\Config\Schema\SchemaIncompleteException: Schema errors for mothermayi.settings with the following errors: mothermayi.settings:_core missing schema, mothermayi.settings:langcode missing schema
This pair of errors indicate a missing or invalid type for
the settings
defined in the
modules's .schema.yml
file. For some background, see
DSE: Why does simpletest report a missing schema for my custom module?
Final word
[TBA]
Last update: 2022-08-02 [gh].