Converting SimpleTests to PHPUnit tests
Some notes on converting SimpleTest-based tests to PHPUnit tests.
Directories and Namespaces
In Drupal 7, SimpleTest-based tests are in the modulename.test
file. This file is expected to contain all the test classes. There are no unique namespaces. Early versions of Drupal 8 also had SimpleTest-based tests, under module_name/src/Tests.
Drupal 8 and later now uses PHPUnit for testing and namespaced classes, in named subdirectories, to organize code and enable PSR-4 autoloading. Simpletest based tests are deprecated. For a contrib module modulename
, this means:
- Tests extending DrupalWebTestCase should be converted to browser tests extending
\Drupal\Tests\BrowserTestBase
- Tests extending DrupalUnitTestCase should be converted to unit tests extending
\Drupal\Tests\UnitTestCase
For DrupalWebTestCase tests, then:
- Create a subfolder in your module:
/tests/src/Functional
- Move your
modulename.test
file into this subfolder. This is for convenience's sake as you refactor the test classes out of this file. - Separate out all of the test classes from
modulename.test
:- If your
modulename.test
file has three test classes in it, you'll end up with three files, one per class. - Each separate file should be named based on a class it contains: for example if your test class name is
MymoduleLoginTest
, the filename should beMymoduleLoginTest.php
. - It's often easier to fully convert one class first, then you have a template for subsequent ones.
- If your
- Give your stub classes their own namespace, and move them into a deeper subfolder. Your tests' namespace is
\Drupal\Tests\modulename\Functional
, and your stubs' namespace should be something like\Drupal\Tests\modulename\Functional\Stubs
. - Mock modules can go in
tests/modules
.
For DrupalUnitTestCase follow the above instructions but replace Functional
with Unit
.
Convert the code
Commenting: The first thing to keep in mind is that this is an excellent opportunity to finish all those docblock and inline comments you always thought you'd get around to, but never did. Go on... It's not that hard. Drupal.org comment doc standards here: https://drupal.org/coding-standards/docs
Drupal Coding Standards: Drupal.org has a SimpleTest coding standards document here: https://drupal.org/node/325974 You should probably adopt these standards, even if you're not a core developer.
Class Names: Drupal searches for tests by finding files that end in Test.php
. This means your test class files should end in Test.php
, which also means your test class names should end in Test
. Clear? :-) If your Drupal 7 testing class has a name like ThisIsATestCase
then you'll need to change it to ThisIsATestCaseTest
for Drupal 8. And you should put it in a file named ThisIsATestCaseTest.php
so that Drupal can find it.
Convert to PHPUnit: Note that Drupal 8 still includes Simpletest but it has been deprecated, all tests should be converted to PHPUnit, see Drupal PHPUnit documentation here: https://drupal.org/node/2116239
Dependencies: One of the more important things is that, in BrowserTestBase
, module dependencies don't live in setUp()
anymore. You'll need to add a static property to your test class called $modules,
which will be an array of module names:
// Drupal 7:
protected function setUp() {
parent::setUp('some_module', 'some_other_module');
}
// Drupal 8:
protected static $modules = ['some_module', 'some_other_module'];
getInfo(): The method getInfo()
should give some metadata for the test in Drupal 7. In Drupal 8 this data should be put in PHPDoc comments. The name
will be taken from the class name and the description
, group
and dependencies
info will be read from the PHPDoc comment for the class. See Test class getInfo() method removed in favor of PHPDoc for full instructions.
drupalPost() and drupalPostForm(): In order to accommodate a form submission to Drupal, BrowserTestBase
has drupalPostForm()
which has the same calling signature as the old drupalPost()
, so it's an easy conversion. Change notice here: https://drupal.org/node/2087433. Using drupalPost()
to do POST requests is not supported in BrowserTestBase since this was mostly used to perform mock Javascript interactions, use a Javascript test for these scenarios. If a POST request is really needed then BrowserTestBase has $this->getHttpClient
available which can be used to perform all kinds of requests.
Extended instructions for converting a WebTestBase test to a BrowserTestBase test
The following instructions are part of #2735005: Convert all Simpletest web tests to BrowserTestBase (or UnitTestBase/KernelTestBase) which tracked the conversion of Drupal core tests. You can find many example conversions linked there.
Instructions for converting a Simpletest web test
- Read https://www.drupal.org/phpunit and all subpages, especially PHPUnit Browser test tutorial.
- Move the test file to a
tests/src/Functional
folder in the module. - Rename the namespace, make the class extend BrowserTestBase and fix the use statement.
- If there is a test method which does not perform any web request (for example no
->drupalGet()
or->drupalPostForm()
calls) then that method should be extracted to a unit test or kernel test.$this->setRawContent()
generally means the test should be converted to a Kernel test. This is best done in a follow-up issue, it is best to first convert it to a BrowserTestBase test. - Calls like
$this->drupalPostAjaxForm()
or$this->drupalPost()
means the test should be converted to a Javascript test. These methods were used to mock javascript interactions in Simpletest, since we have proper Javascript testing in PHPUnit, there is no longer a need to mock this behavior. This is best done in a follow-up issue, it is best to first convert it to a BrowserTestBase test. In most casesdrupalPostAjaxForm
can be replaced bydrupalPostForm
and the test will continue to work. - Otherwise, try to change as little as possible in the converted web test. Do not get tempted to "improve" or "tidy up" the tests - the conversion should be easy to review with only the minimum necessary changes. Further cleanup can be done in a follow-up issue.
- Run the single test file with PHPUnit (replace the path with the file you converted):
cd core ../vendor/bin/phpunit modules/help/tests/src/Functional/HelpTest.php --stop-on-failure
- If the conversion starts to involve complex changes, please reach out to people or search the core conversion issues for similar scenarios and see how they were solved.
- Any TestBase or Trait which is in the old
/src/Tests/
location should be moved to the/tests/
dir
Common conversion issues
- When using the result of an XPath or cssSelect and the result is used as an array or cast to a string, this would work in WebTestBase but for BrowserTestBase this returns a
\Behat\Mink\Element\NodeElement
object which does not allow this. Usually callinggetText()
on the result fixes the problem. - When you see an exception of
Exception: Serialization of 'Closure' is not allowed
This is usually also caused by the return of an XPath being used as a string or array in an assertion. Again, adding
getText()
on the result usually fixes this problem. Since the exception doesn't specify on which line of the test the error occurs, this can be very hard to debug. drupalPostAjaxForm()
doesn't exist on BrowserTestBase and can usually be replaced withdrupalPostForm()
this does mean a loss of pseudo-JavaScript coverage, so ideally a follow up should be created to add proper JavaScript coverage using WebdriverTestBasedrupalGetTestFiles()
doesn't exist on BrowserTestBase, you need to add theTestFileCreationTrait
and rename the method togetTestFiles()
(or rename the method in the use statement to minimize changes in the rest of the file)
Help improve this page
You can:
- Log in, click Edit, and edit this page
- Log in, click Discuss, update the Page status value, and suggest an improvement
- Log in and create a Documentation issue with your suggestion