PHPUnit
PHPUnit by Sebastian Bergmann is an advanced unit testing framework for PHP. It is installed as Composer dependency and is not part of Moodle installation. To run PHPUnit tests, you have to manually install it on your development computer or test server.
Read the excellent guide at
Installation of PHPUnit via Composer
- Install Composer Instructions for installing composer on all platforms are here: https://getcomposer.org/download/
Install the composer.phar file to your moodle folder.
- Execute Composer installer
cd /your/moodle/dirroot
php composer.phar install
If that gives you connection problems try
php composer.phar install --prefer-source
Troubleshooting:
- On Windows if you are behind a proxy you will need to setup an environment variable called
HTTP_PROXYwith a value detailing your HTTP Proxy address and port before composer will correctly download files. - You may be prompted for github credentials when installing composer dependencies.
- This is used to generate an personal access token to avoid being rate limited by github.
- If you have Two Factor Authentication enabled on your github account, or do not wish to supply your own credentials you will need to generate a token manually:
- Visit https://github.com/settings/applications and request personal access token
- Copy this token and add it to your ~/.composer/config.json
- ( See composer issue #2280 for further details of this bug.)
Detailed instructions:
- Composer documentation Detailed instructions:
- PHPUnit installation in Windows
- PHPUnit installation in OS X
PHPUnit versions
The following table shows what PHPUnit version is installed in which Moodle version when using the default composer setup.
| Moodle version | PHPUnit version | Links |
|---|---|---|
| Moodle 5.0 | PHPUnit 11.4 | Documentation |
| Moodle 4.4 - 4.5 | PHPUnit 9.6 | Documentation |
| Moodle 3.11 - 4.3 | PHPUnit 9.5 | Documentation |
| Moodle 3.10 | PHPUnit 8.5 | Documentation |
| Moodle 3.7 - 3.9 | PHPUnit 7.5 | Documentation |
| Moodle 3.4 - 3.6 | PHPUnit 6.5 | Documentation |
| Moodle 3.2 - 3.3 | PHPUnit 5.5 | Documentation |
| Moodle 3.1 | PHPUnit 4.8.27 | Documentation 4.8 |
Initialisation of test environment
Our PHPUnit integration requires a dedicated database and data root folder. First, add a new data root directory and prefix into your config.php, you can find examples in config-dist.php.
$CFG->phpunit_prefix = 'phpu_';
$CFG->phpunit_dataroot = '/home/example/phpu_moodledata';
Some PHPUnit tests require a live internet connection. If your system does not have a direct connection to the Internet, you also need to specify your proxy in config.php - even though you normally specify it by using the admin settings user interface. (If you do not use a proxy, you can skip this step.) Check the settings on the relevant admin settings page, or from the mdl_config table in your database, if you are unsure of the correct values.
// Normal proxy settings
$CFG->proxyhost = 'wwwcache.example.org';
$CFG->proxyport = 80;
$CFG->proxytype = 'HTTP';
$CFG->proxybypass = 'localhost, 127.0.0.1, .example.org';
// Omit the next lines if your proxy doesn't need a username/password:
$CFG->proxyuser = 'systemusername';
$CFG->proxypassword = 'systempassword';
You can also provide specific database settings for unit testing (if these are not provided, the standard database settings will be used):
$CFG->phpunit_dbtype = 'pgsql'; // 'pgsql', 'mariadb', 'mysqli', 'mssql', 'sqlsrv' or 'oci'
$CFG->phpunit_dblibrary = 'native'; // 'native' only at the moment
$CFG->phpunit_dbhost = '127.0.0.1'; // eg 'localhost' or 'db.isp.com' or IP
$CFG->phpunit_dbname = 'mytestdb'; // database name, eg moodle
$CFG->phpunit_dbuser = 'postgres'; // your database username
$CFG->phpunit_dbpass = 'some_password'; // your database password
Then you need to initialise the test environment using following command.
cd /home/example/moodle
php admin/tool/phpunit/cli/init.php
This command has to be repeated after any upgrade, plugin (un)installation or if you have added tests to a plugin you are developing:
NOTE: make sure that your php cli executable (or the one you want to use) is correctly on your path as the individual init scripts will call it repeatedly. Also, ensure en_AU locale is installed on your server.
LDAP
If you want to run LDAP unit tests you must have a working, configured LDAP environment on your test server. There must be a basic LDAP tree structure in place or tests will fail with "Search: No such object". Build an LDAP tree structure in a new shell prompt:
ldapadd -H ldap://127.0.0.1 -D "cn=admin,dc=yourcomputer,dc=local" -W
dn:dc=yourcomputer,dc=local
objectClass:dcObject
objectClass:organizationalUnit
dc:yourcomputer
ou:YOURCOMPUTER
In config.php tell Moodle where to look for your test LDAP environment:
// Constants for auth/ldap tests.
define('TEST_AUTH_LDAP_HOST_URL', 'ldap://127.0.0.1');
define('TEST_AUTH_LDAP_BIND_DN', 'cn=admin,dc=yourcomputer,dc=local');
define('TEST_AUTH_LDAP_BIND_PW', '*');
define('TEST_AUTH_LDAP_DOMAIN', 'dc=yourcomputer,dc=local');
// Constants for enrol/ldap tests.
define('TEST_ENROL_LDAP_HOST_URL', 'ldap://127.0.0.1');
define('TEST_ENROL_LDAP_BIND_DN', 'cn=admin,dc=yourcomputer,dc=local');
define('TEST_ENROL_LDAP_BIND_PW', '*');
define('TEST_ENROL_LDAP_DOMAIN', 'dc=yourcomputer,dc=local');
// Constants for lib/ldap tests.
define('TEST_LDAPLIB_HOST_URL', 'ldap://127.0.0.1');
define('TEST_LDAPLIB_BIND_DN', 'cn=admin,dc=yourcomputer,dc=local');
define('TEST_LDAPLIB_BIND_PW', '*');
define('TEST_LDAPLIB_DOMAIN', 'dc=yourcomputer,dc=local');
Maintaining the test environment
Once the environment has been initialised, the admin/tool/phpunit/cli/util.php script can manage it without a full re-initialisation via init.php. Run it with --help to see all of the available options:
php public/admin/tool/phpunit/cli/util.php --help
Upgrading the environment after a change
When you add, remove, or change a plugin (including bumping a plugin's version number), the test environment no longer matches the code and PHPUnit will refuse to run:
Moodle PHPUnit environment was initialised for different version, please use:
php public/admin/tool/phpunit/cli/init.php
or php public/admin/tool/phpunit/cli/util.php --upgrade
Instead of re-initialising the whole environment, you can upgrade just the installed plugins in place, which is significantly faster:
php public/admin/tool/phpunit/cli/util.php --upgrade
This runs the standard non-core upgrade against the existing test database — installing new plugins, running their upgrade steps, and applying plugin default settings — in the same way that completing an upgrade from the notifications page does on a normal site.
Snapshots
Initialising the test environment from scratch is slow because it installs the full Moodle database. In continuous integration, where the same vanilla install is rebuilt for every job, this can dominate the run time. Snapshots let you capture an initialised environment once and restore it almost instantly on later runs.
A snapshot is a single .zip archive containing the test database structure and data together with the filedir. Snapshots are stored under <phpunit_dataroot>/snapshots/ and are keyed by database type and Moodle version, so a snapshot taken for one version is not used against another.
Creating a snapshot
Initialise the environment as usual, then create a snapshot:
php public/admin/tool/phpunit/cli/util.php --snapshot
By default the snapshot is named <dbtype>-snapshot-<version> (for example pgsql-snapshot-2026072200). You can give it a custom name with --snapshot=NAME:
php public/admin/tool/phpunit/cli/util.php --snapshot=mysnapshot
Listing snapshots
php public/admin/tool/phpunit/cli/util.php --list
The names printed are exactly the values to pass to --restore:
Available snapshots (use the full name shown with --restore=NAME):
- pgsql-mysnapshot-2026072200
- pgsql-snapshot-2026072200
Restoring a snapshot
The database must be empty before restoring, so drop it first:
php public/admin/tool/phpunit/cli/util.php --drop
php public/admin/tool/phpunit/cli/util.php --restore=pgsql-snapshot-2026072200
--restore rebuilds the database and filedir from the snapshot, which is much faster than a full init.php. Pass the exact name shown by --list (without the .zip extension). Snapshots are preserved by --drop, so you can drop and restore repeatedly.
Example CI workflow
The snapshot archive is intended to be cached by your CI system (GitHub Actions, GitLab CI, Jenkins, and so on). How the archive is stored between jobs is out of scope for Moodle and depends on your CI provider.
-
On a vanilla Moodle, initialise the environment and take a snapshot, then cache the archive from
<phpunit_dataroot>/snapshots/keyed by the Moodle version:php public/admin/tool/phpunit/cli/init.php
php public/admin/tool/phpunit/cli/util.php --snapshot -
On subsequent runs, restore the cached snapshot instead of re-installing:
php public/admin/tool/phpunit/cli/util.php --restore=<name> -
Install the plugin under test, then upgrade the environment in place:
php public/admin/tool/phpunit/cli/util.php --upgrade -
Run the tests.
If a new Moodle version is released the cached snapshot no longer matches, and the workflow should fall back to a fresh init.php before taking a new snapshot.
Test execution
To execute all test suites from main configuration file execute the vendor/bin/phpunit script from your $CFG->dirroot directory.
cd /home/example/moodle
vendor/bin/phpunit
The rest of examples uses phpunit, please substitute it with vendor/bin/phpunit or create a shortcut in your $CFG->dirroot.
In IDEs, you may need to specify the path to the PHPUnit configuration file. Use the absolute path to phpunit.xml from your $CFG->dirroot.
How to run only some tests
Running a single test quickly (PHPUnit 9)
The fastest way to run a single test in PHPUnit 9.5 and higher is to use the filter argument:
vendor/bin/phpunit --filter metadata_registry_test
vendor/bin/phpunit --filter test_get_registry_metadata_count
To run all tests provided by the single component, use suite and the name it has in the phpunit.xml file. Example:
vendor/bin/phpunit --testsuite workshopform_accumulative_testsuite
Alternatively if you have config files built for each component:
vendor/bin/phpunit -c mod/workshop/form/accumulative/
You can also run a single test by using test file path:
vendor/bin/phpunit my/tests/filename.php
so, run this command in the CLI to see a real test in action:
vendor/bin/phpunit cohort/tests/lib_test.php
You can also run a single test method inside a class:
vendor/bin/phpunit --filter test_function_name path/to/file.php
Note: You should be careful because it may be possible to run tests this way which are not included in the normal run if, for example, the file is not placed in the correct location. If you use this method, do at least one full test run (or --group run, as below) to ensure the test can be found.
Filters can also be applied to capture a group of similar tests across all testsuites:
vendor/bin/phpunit --filter test_flag_user
It is also possible to run all tests in a component (subsystem or plugin) by using the testsuite option:
vendor/bin/phpunit --testsuite mod_forum_testsuite
vendor/bin/phpunit --testsuite core_privacy_testsuite
vendor/bin/phpunit --testsuite core_privacy_testsuite --filter test_component_is_compliant
Using the @group annotation
If you add annotations like
namespace qtype_stack;
/**
* Unit tests for {@link stack_cas_keyval} @ qtype/stack/tests/cas_keyval_test.php.
* @group qtype_stack
*/
class cas_keyval_test extends \basic_testcase {
to all the classes in your plugin, then you can run just the tests for your plugin by doing
phpunit --group qtype_stack
Therefore, it is suggested that you annotate all your tests with the Frankenstyle name of your plugin.
Using multiple phpunit.xml files
It's easy to create alternative phpunit.xml files defining which tests must be run together. For reference, take a look to the default phpunit.xml available in your base directory once the testing environment has been initialised. After creating the custom file you will be able to run those tests with
vendor/bin/phpunit -c path/to/your/alternative/phpunit/file.xml
Also, for commodity, you can use this command:
php admin/tool/phpunit/cli/util.php --buildcomponentconfigs
It will, automatically, create one valid phpunit.xml file within each component (plugin or subsystem) and other important directories, so later you will be able to execute tests like
vendor/bin/phpunit -c mod/forum[](/phpunit.xml) // Note that it's not needed to specify the name of the file (if it is 'phpunit.xml').
vendor/bin/phpunit -c question
vendor/bin/phpunit -c question/type/calculated
vendor/bin/phpunit -c backup
vendor/bin/phpunit -c lib/dml
or, also
cd directory/with/phpunit.xml
phpunit
External test resources
By default Moodle phpunit tests contact http://download.moodle.org server when testing curl related functionality. Optionally you may checkout a local copy of the test scripts and access it instead:
- clone https://github.com/moodlehq/moodle-exttests to web directory
- add to your config.php or modify phpunit.xml file
define('TEST_EXTERNAL_FILES_HTTP_URL', 'http://localhost/moodle-exttests');
Writing new tests
PHPUnit support in IDEs
Common Unit Test Problems
Command line tips
- Display each test name before running it (useful e.g. if it crashes before the end and you want to know which test it was running at that point)
vendor/bin/phpunit --debug
(you will probably want to redirect this to a file as it gets very long).