Deeper Testing with PHPUnit Markup Assertions

In a perfect world, every piece of software would have automated tests. As soon as we change a line, we as developers would know what, if anything, broke in our application and where we need to look to fix it. Unfortunately, we don’t live in a perfect world, so we get by doing what we can.

Still, we can look to our image of the perfect world and draw from it, molding and shaping what we do have to closer resemble what we’ve been longing for.

Testing in a “perfect” world

The Test Automation Period, with unit tests at the base, then building to API and finally UI tests.

Graphic by Seth Urban

A few years ago, I was introduced to the concept of The Test Automation Pyramid, a concept born out Agile programming where your tests are split into three “levels” of testing. At the base, you have unit tests, which are designed to test the smallest increment possible of your code (typically a single function or method). Because unit tests are typically only testing a single method, they’re often extremely fast to both write and run. As the base of the pyramid, it’s also expected that unit tests will make up the majority of the tests for your application.

Next up the pyramid, you have your API or service layer tests. These start introducing more complicated scenarios, where you might be writing to a database, testing interactions with third-party services, or making sure your controller is responding to requests with reasonable data. Though the terminology often gets muddled, these are also typically known as integration tests. API tests are not typically as fast nor plentiful as our unit tests (after all, we’re typically booting and interacting with a lot more code), and they may be more involved to write, but they help ensure the various parts of our application can work well together.

Finally, up at the top of the Test Automation Pyramid, we reach the UI tests, where we’re testing the end-to-end functionality of the application. These tests can go as far as literally scripting user interactions with the front-end of the application, and are meant to describe the exact path users might take. Since these tests are the most involved, end-to-end testing is often considerably slower than those down the pyramid, and will typically make up the smallest percentage of your test suite.

Testing in an imperfect world

As I said above, the Automated Testing Pyramid describes an ideal arrangement and ratio of tests. There are certainly projects where a full, end-to-end UI testing framework would be overkill, and when writing tests around existing (read: legacy) code, we’ll take whatever we can get.

On a recent project, I was tasked with rebuilding a plugin’s settings screen within the WordPress admin UI. I had plenty of tests to cover how data was being saved, that nonces were being checked, and all of the other typical pain points of a WordPress plugin settings page, but there was a glaring hole in my test coverage: how could I assert that the markup being generated matched what I was expecting?

In some cases, you can get by using PHPUnit’s assertContains() assertion:

Unfortunately, if someone changes the order of “notice” and “notice-error”, your test will fail because that exact string won’t be found in the output.

Other tests may rely on hard-coded strings that go out the window if/when the copy ever changes:

This comes with its own problem: if anyone ever changes the template string of the show_greeting() function from Hi, %s! to Hello, %s!, our test will break (whether we want it to or not).

“That’s okay,” you reassure yourself, “I’ll just use regular expressions instead!”. I warn you, as someone who actually loves writing regular expressions: you’re in for a world of hurt.

Testing markup with PHPUnit Markup Assertions

This is the part of the post where I typically say “but wait, there’s a better way!”

But wait, there’s a better way!

Sick of having to piece together fragile tests for markup where I needed to make assertions but didn’t want to bring in a whole UI testing framework, I spent a bit of time and created the PHPUnit Markup Assertions library.

PHPUnit Markup Assertions introduces a MarkupAssertionsTrait trait that can be applied to your PHPUnit test cases. Under the hood, it uses Zend’s DOM component to construct PHP representations of DOM elements without all of the “gotchas” of DOMDocument, while providing a more CSS-like selector interface.

With PHPUnit Markup Assertions, we have access to new PHPUnit assertions like assertContainsSelector() and assertSelectorCount().

Full installation instructions for PHPUnit Markup Assertions are available on GitHub, but the general approach is:

Once installed, apply the MarkupAssertionsTraitto any test cases you wish to use it on:

That’s it! With PHPUnit Markup Assertions installed and loaded, you’ll have access to all of its new assertions.

Leave a Reply