Upgrading Drupal’s Viewport module to Drupal 8 (Part 3 – Final)

Drupal 7 to D8 migration

Image taken from https://drupalize.me

With parts one and two of the series covered, this 3rd (and final!) part will cover some other important aspects of Drupal development that I wanted to pay attention to while working on the Drupal 8 version of the viewport module.

Essentially, the kind of aspects that are easy to forget (or ignore on purpose) when a developer comes up with a working version or something “good enough” to be released, and thinks there’s no need to write tests for the existing codebase (we’ve all done it at some point).

This post will be a mix of links to documentation and resources that were either useful or new to me, and tips about some of the utilities and classes available when writing unit or functoinal tests.

Testing

Since I wrote both functional tests and unit tests for the viewport module, this section is split in two parts, for the sake of clarity and structure.

- Functional tests

Before getting into the bits and blobs of functional test classes, I decided read a couple articles on the matter, just to see how much testing had changed in the last year(s) in Drupal 8. This article on the existing types of Drupal 8 tests was a good overview, as well as Acquia’s lesson on unit and functional tests.

Other than that, there were also a few change notices I went through in order to understand what was being done with the testing framework and why. TL;DR: Drupal runs away from simpletest and moves to PHPUnit to modernise the test infrastructure. That started by adding the PHPUnit test framework to Drupal core. Also, new classes were added that leveraged PHPUnit instead of the existing Simpletest-based classes. Specifically, a new KernelTestBase was added, and also a BrowserTestBase class, replacing the well-known WebTestBase.

I decided to base all my tests in the PHPUnit classes exclusively, knowing that Simpletest will just die at some point. One of the key requirements for this was to put all test classes in a different path: {module-folder}/tests/src/{test-type}/{test-classes}. Also, the @group annotation was still required, as with Simpletest, so that tests of specific groups or modules can be executed alone, without all the test suite running.

The first thing I noticed when I started to write the first Functional test class, ViewportPermissionsTest, was that the getInfo() method was no longer needed, since it’s been removed in favor of PHPDoc, which means all the info there is retrieved from the documentation block of the test class.

With PHPUnit introduced in Drupal 8, a lot of new features have been added to test classes through PHP Annotations. Without getting into much details, two of them that called my attention when trying to debug a test, were the @runTestsInSeparateProcesses and @preserveGlobalState  annotations. Most of the time the defaults will work for you, although there might be cases where you may want to change them. Note that running tests in separate processes is enabled by default, but some performance issues have been reported on this feature for PHPUnit.

I also came across some issues about the lack of a configuration schema defined for my module, when trying to execute tests in the ViewportTagPrintedOnSelectedPagesTest class, which led me to find another change notice (yeah… too many!) about tests enforcing strict configuration schema adherence by default. The change notice explains how to avoid that (if really necessary), but given that’s not a good practice, you should just add a configuration schema to all your modules, if applicable.

Some other scattered notes of things I noticed when writing functional tests:

  • drupalPostForm() $edit keys need to be the HTML name of the form field being tested, not the field name as defined in the Form API $form array. Same happens with drupalPost().
  • When working on a test class, $this->assertSession()->responseContains() should be used to check HTML contents returned on a page. The change notice about BrowserTestBase class, points to $this->assertSession()->pageTextContains(), but that one is useful only for actual contents displayed on a page, and not the complete HTML returned to the browser.

- Unit Tests

The main difference between unit tests and functional tests (speaking only of structure in the codebase), is that unit tests need to be placed under {module-name}/tests/src/Unit, and they need to extend the UnitTestCase class.

Running PHPUnit tests just requires executing a simple command from the core directory of a Drupal project, specifying the testsuite or the group of tests to be executed, as shown below:

php ../vendor/bin/phpunit –group=viewport

php ../vendor/bin/phpunit –group=viewport –testsuite=unit

As mentioned in the link above, there has to be a properly configured phpunit.xml file within the core folder. Also, note that Drupal.org’s testbot runs tests in a different way. As detailed in the link above, tests can be executed locally in the same way the bot does, to ensure that the setup will allow a given module to receive automated testing support once contributed to Drupal.org.

PHPUnit matchers: In PHPUnit parlance, a matcher is ultimately an instance of an object that implements the PHPUnit_Framework_MockObject_Matcher_Invocation interface. In summary, it helps to define the result that would be expected of a method call in an object mock, depending on the arguments passed to the method and the number of times it’s been called. For example:

That’s telling the pathMatcher mock (don’t confuse with a PHPUnit matcher), to return the value “/frontpage-path” whenever the method getFrontPagePath() is called. However, this other snippet:

It’s telling the mock to return the value “/third-time”, only when the getFrontPagePath() method is executed exactly 3 times.

will(): As seen in the examples above, the will() method can be used to tell the object mock to return different values on consecutive calls, or to get the return value processed by a callback, or fetch it from a values map.

These concepts are explained in more detail in the Chapter 9 of the PHPUnit manual.

Coding Standards and Code Sniffer

With the module fully working, and tests written for it, the final stage was to run some checks on the coding standards, and ensure everything is according to Drupal style guides. Code Sniffer makes this incredibly easy, and there’s an entire section dedicated to it in the Site Building Guide at Drupal.org, which details how to install it and run it from the command line.

Finally, and even though I didn’t bother changing the README.txt contents, I also noticed the existence of a README Template file available in the online documentation, handy when contributing new modules. With all the codebase tidied up and proper tests in place, the module was finally good to go.

That’s it! This ends the series of dev diaries about porting the Viewport module to D8. I hope you enjoyed it! There might be a similar blog post about the port process of the User Homepage module, so stay tuned if you’re interested in it.

Links and (re)sources

As usual, I’ll leave a list of the articles and other resources I read while working on the module upgrade.

  • Which D8 test is right for me?: link.
  • Acquia’s lesson on Unit and Functional tests: link.
  • Simpletest Class, File and Namespace structure (D8): link (note Simpletest wasn’t used in the end in the module port).
  • Converting D7 SimpleTests to D8: link.
  • Configuration Schema and Metadata: link.
  • Drupal SimpleTest coding standards: link
  • PHPUnit in Drupal 8: link.
  • Agile Unit Testing: link.
  • Test Doubles in PHPUnit: link.
  • Drupal coding standards: link.
  • README Template file: link.

Change Records and topic discussions

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">