Convert Simpletest to PHPUnit

Soumis par GoZ le mer 29/06/2016 - 19:16

Convert Simpletest to PHPUnit

The historical test tool of Drupal Simpletest is deprecated with Drupal 8 and will be removed from Drupal 9. So tests should be converted from simpletest to PHPUnit.

It's hard to find replacements for old asserts or specials tests with PHPUnit. Solutions can be found in existing tests in modules and issues, but now issue group all replacements.

First, i advise you to follow blog post from Matt Glaman on Commerce Guys about PhpStorm Configuration to execute PHPUnit tests with PhantomJS.

Then i'll put in this post all replacements i found.

Simpletest/WebTestBase Phpunit/Mink
assertEqual() assertEquals()
assertEqual(count()) assertCount()
assertFalse() assertEmpty()
assertField($name) assertSession()->fieldExists($name)
assertFieldByName() assertSession()->fieldValueEquals() or assertSession()->fieldExists($name)
assertFieldByXPath($xpath) use xpath() or wait after #2784537 issue
assertFieldChecked() assertSession()->checkboxChecked()
For a button: assertFieldsByValue() or assertFieldById () or assertFieldByName () assertSession()->buttonExists()
assertIdentical() assertSame()
assertLink($value) assertNotEmpty($this->getSession()->getPage()->hasLink($value))
assertNoLink($value) assertEmpty($this->getSession()->getPage()->hasLink($value))
assertNoFieldChecked() assertSession()->checkboxNotChecked
assertNoText() assertSession()->pageTextNotContains()
assertOptionSelectedWithDrupalSelector()  custom assertOptionSelected()
assertRaw($text) assertSession()->pageTextContains($text)
assertResponse() assertSession()->statusCodeEquals() or assertSession()->responseContains()
assertText() assertSession()->pageTextContains()
assertTrue() assertNotEmpty()
cssSelect($arguments) getSession()->getPage()->find('css', $arguments);
clickLink() getSession()->getPage()->clickLink()
drupalCreateContentType() NodeType::create() use Drupal\node\Entity\NodeType;
drupalGetNodeByTitle() getNodeByTitle()
drupalPlaceBlock() placeBlock() use Drupal\simpletest\BlockCreationTrait;
drupalPostForm($url, $values, $button_text); submitForm($values, $button_text);
drupalPostAjaxForm() use JavascriptTestBase. See How to deal with Javascript base tests section.
xpath($arguments) getSession()->getPage()->find('xpath', $arguments)

How to get rendered HTML

In case you want to debug the html rendered during test, use the following code :

$this->getSession()->getPage()->getHtml()

How to deal with Javascript base tests

If you have to fill fields with ajax events, you should use specific methods and not sending them directly in submitForm(). Worst, you have to filled them before the submission, waiting for ajax response using custom method waitForAjaxToFinish()you can see below and not sending them in $edit array of submitForm().

To use javascript tests, the class test has to extend Drupal\FunctionalJavascriptTests\JavascriptTestBase instead of BrowserTestBase.

Below is example of testing code with ajax response.

/**
  * Example of test.
  */
  function testExample() {
    // Get page with form.
    $this->drupalGet('admin/config/regional/zones/add');
    $session = $this->getSession();

    // Select country value from plugin select list and submit 'Add' button
    // which call ajax to display another form.
    //
    // Add a Country zone member, select the US.
    $this->submitForm(['plugin' => 'country'], t('Add'));
    // Wait after end of ajax call so new form appear.
    $this->waitForAjaxToFinish();
    // Fill field added by ajax call.
    $session->getPage()->fillField('members[0][form][name]', 'California');
    // A javascript event onChange or something similar exists on country_code
    // field that execute ajax to load new fields depending of country.
    // So this field has to be filled by javascript and to pushed directly with
    // submitForm().
    $session->getPage()->fillField('members[0][form][country_code]', 'US');
    // Wait after end of ajax call so new fields appear.
    $this->waitForAjaxToFinish();

    // Submit the form.
    // It's the final POST submission we want without javascript.
    // In this case, as we extend JavascriptTestBase, submitForm() will still
    // execute javascript.
    // As members[0][form][country_code] has javascript event with ajax call, if
    // you send it here, ajax are called, fields are replaced by response. This
    // does not act like a final submission does clicking on submit button and
    // data are missing.
    // So be sure to not send country_code field or other field with javascript
    // event in $edit array of submitForm().
    //
    // Finish creating the zone and zone members.
    $edit = [
      'id' => 'test_zone',
      'name' => 'Test zone',
      'scope' => $this->randomMachineName(6),
      'members[0][form][name]' => 'California',
      'members[0][form][administrative_area]' => 'US-CA',
      'members[0][form][included_postal_codes]' => '123',
      'members[0][form][excluded_postal_codes]' => '456',
    ];
    $this->submitForm($edit, t('Save'));
  }

  /**
   * Waits for jQuery to become active and animations to complete.
   */
  protected function waitForAjaxToFinish() {
    $condition = "(0 === jQuery.active && 0 === jQuery(':animated').length)";
    $this->assertJsCondition($condition, 10000);
  }

How to make screenshots to help you debugging PhantomJS calls

The following method found on issue #2702661 can help debugging your javascript tests. Thanks to that method, you can made a screenshot and see what's going on phantomJS.

/**
 * Creates a screenshot.
 *
 * @param string $filename
 *   The file name of the resulting screenshot. If using the default phantomjs
 *   driver then this should be a JPG filename.
 *
 * @throws \Behat\Mink\Exception\UnsupportedDriverActionException
 *   When operation not supported by the driver.
 * @throws \Behat\Mink\Exception\DriverException
 *   When the operation cannot be done.
 */
protected function createScreenshot($filename, $background = 'white') {
  $session = $this->getSession();
  if (!empty($background)) {
    $session->executeScript("document.body.style.backgroundColor = '" . $background . "';");
  }
  $image = $session->getScreenshot();
  file_put_contents($filename, $image);
}

Custom assertOptionSelected()

In case you want to check that a specific option is selected, you can use the code below.

Note, it's recommended to not use tag id for field tests but attribute name.

use Drupal\Component\Render\FormattableMarkup;

/**
   * Asserts that a select field has a selected option.
   *
   * @param string $id
   *   ID of select field to assert.
   * @param array $option
   *   Option to assert.
   * @param string $message
   *   (optional) A message to display with the assertion. Do not translate
   *   messages: use \Drupal\Component\Utility\SafeMarkup::format() to embed
   *   variables in the message text, not t(). If left blank, a default message
   *   will be displayed.
   */
  protected function assertOptionSelected($id, $option, $message = '') {
    $elements = $this->xpath('//select[@name=:id]//option[@value=:option]', array(':id' => $id, ':option' => $option));
    foreach ($elements as $element) {
      $this->assertNotEmpty($element->isSelected(), $message ? $message : new FormattableMarkup('Option @option for field @id is selected.', array('@option' => $option, '@id' => $id)));
    }
  }