Converting SimpleTests to PHPUnit tests

Last updated on
17 October 2021

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:

  1. Create a subfolder in your module: /tests/src/Functional
  2. Move your modulename.test file into this subfolder. This is for convenience's sake as you refactor the test classes out of this file.
  3. 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 be MymoduleLoginTest.php.
    • It's often easier to fully convert one class first, then you have a template for subsequent ones.
  4. 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.
  5. 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

  1. Read https://www.drupal.org/phpunit and all subpages, especially PHPUnit Browser test tutorial.
  2. Move the test file to a tests/src/Functional folder in the module.
  3. Rename the namespace, make the class extend BrowserTestBase and fix the use statement.
  4. 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.
  5. 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 cases drupalPostAjaxFormcan be replaced by drupalPostFormand the test will continue to work.
  6. 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.
  7. 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
    
  8. 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.
  9. 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 calling getText()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 with drupalPostForm()this does mean a loss of pseudo-JavaScript coverage, so ideally a follow up should be created to add proper JavaScript coverage using WebdriverTestBase
  • drupalGetTestFiles()doesn't exist on BrowserTestBase, you need to add the TestFileCreationTrait and rename the method to getTestFiles() (or rename the method in the use statement to minimize changes in the rest of the file)

Help improve this page

Page status: No known problems

You can: