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
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.
2 unit tests. 0 integration tests. pic.twitter.com/ieNQWRFRUJ
— DEV Community (@ThePracticalDev) April 8, 2017
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:
1 2 3 4 5 6 |
function testDisplaysErrorMessage() { $this->assertContains( '<div class="notice notice-error">', render_settings_screen() ); } |
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:
1 2 3 4 5 6 7 |
function testShowGreeting() { $user = $this->factory->user()->create( [ 'first_name' => 'Joe', ] ); $this->assertEquals( 'Hi, Joe!', show_greeting( $user ) ); } |
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()
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
use SteveGrunwell\PHPUnit_Markup_Assertions\MarkupAssertionsTrait; class RelatedPostTest extends WP_UnitTestCase { use MarkupAssertionsTrait; public function testRendersRelatedPostsBox() { // Set up whatever is needed to populate related posts. // Capture the output of your related posts function. $output = get_related_posts_box(); $this->assertSelectorCount(5, 'li.related-post', $output); $this->assertContainsSelector('#related-posts-box', $output); } } |
Full installation instructions for PHPUnit Markup Assertions are available on GitHub, but the general approach is:
1 |
$ composer require --dev stevegrunwell/phpunit-markup-assertions |
Once installed, apply the MarkupAssertionsTrait
to any test cases you wish to use it on:
1 2 3 4 5 |
class ExampleTest extends WP_UnitTestCase { use SteveGrunwell\PHPUnit_Markup_Assertions\MarkupAssertionsTrait; // Your test methods. } |
That’s it! With PHPUnit Markup Assertions installed and loaded, you’ll have access to all of its new assertions.
Leave a Reply