Ralph J. Smit Laravel Software Engineer
When building applications, a decent testsuite is an absolute must-have. In the short run it might sound like a waste of time ("I'm sure I implemented this feature correctly"), but one year later you'll not be so sure anymore when you need to make a few changes.
To make testing somewhat more pleasant, you can write your own custom testing helper functions that make your tests more expressive and easy-to-read. In this article, I'll show you how to test your own testing helpers/assertions with PHPUnit.
Custom PHPUnit assertions
In order to start testing our own helpers, we'll first need a test. Let's start with this example test, that asserts whether a string is a greeting:
<?php namespace Tests\Unit; use Tests\TestCase; class HelpersTest extends TestCase{ /** @test */ public function assert_string_is_a_greeting() { $stringA = 'Hello!'; $stringB = 'Bye!'; $this->assertTrue($stringA === 'Hello!'); $this->assertFalse($stringB === 'Hello!'); }}
Now, let's make a simple helper that accepts a string
and tests whether this string is a greeting. For convenience sake, I'll add this helper to a protected
function in the same file, but this helper can be anywhere. A good place to put your custom assertions is in a separate package.
Let's write two helpers:
use PHPUnit\Framework\Assert; protected function assertGreeting(string $potentialGreeting): void{ Assert::assertStringContainsString('Hello', $potentialGreeting);} protected function assertNotGreeting(string $potentialGreeting): void{ Assert::assertStringNotContainsString('Hello', $potentialGreeting);}
And use it like this:
/** @test */public function assert_string_is_a_greeting(){ $stringA = 'Hello!'; $stringB = 'Bye!'; $this->assertGreeting($stringA); $this->assertNotGreeting($stringB);}
This is way better, right? Much more concise and also much more clear what you're exactly testing, in particular if you have team members.
Testing your testing helpers
But how do we know that our assertGreeting()
and assertNotGreeting()
helpers are asserting the right thing? In theory, they could be asserting totally different things and you're tests would still pass. Let's write a test for your own assertions!
In order to simplify the process, I'll add the test in the same file again. If you're putting your testing helpers in a package, you could add a test in your package tests folder.
First, let's write a test that we can correctly assert a greeting, so the happy path. How do we do that?
When a validation fails, PHPUnit will throw an AssertionFailedError
exception. We can catch that exception or specify that we expect such an exception.
In order to test a custom helpers, we can do the following:
-
Input a few things that should pass, and do nothing. The helper should never fail, so the tests will be green.
-
Input a few things that should not pass, and say that we expect an
AssertionFailedError
exception. The tests should also never fail now, because we say that we expect an exception.
So if we say that we don't expect an AssertionFailedError
for tests that should pass, and expect an AssertionFailedError
for tests that should not pass, we can test our helper.
Using a dataprovider
Because we want to test as many inputs and 'outputs' as possible, I'm going to make use of a dataprovider.
If you haven't used a PHPUnit dataprovider before, don't worry, it's very easy! It basically means that you have a function (which is the dataprovider) that returns an array. Each item in the array should be another array. PHPUnit will run the test for every item. That means that you don't have to write ten very similar tests, but you can just use a single test plus a dataprovider for it.
First, we'll create our dataprovider:
protected function greetingProvider(): array{ return [ ['Hello', true,], ['Hello there!', true,], ['Bye', false,], ['Bye bye', false,], ['See you later', false,], ];}
This dataprovider returns a multidimensional array. The first item in the 'child-arrays' is the greeting we want to test, and the second item is whether this is indeed a greeting.
Writing a test for our PHPUnit helper
Now, let's write a test for our assertGreeting()
helper:
/** * @test * @dataProvider greetingProvider */public function can_correctly_assert_greetings(string $input, bool $isGreeting): void{ if ( ! $isGreeting ) { $this->expectException(AssertionFailedError::class); } $this->assertGreeting($input);}
Let's break this down. First we're going to mark this as a @test
and assign the greetingProvider()
function as a dataprovider. Then, we're saying: if the $input
isn't a greeting, expect an AssertionFailedError
exception to be thrown.
Last, we're going to run the assertion. Not that difficult, right?
Testing the `assertNotGreeting()` helper
We can also test our assertNotGreeting()
helper in the same way as the first one. We'll only expect an exception here if the $input
is a greeting:
/** * @test * @dataProvider greetingProvider */public function can_correctly_assert_not_greetings(string $input, bool $isGreeting): void{ if ( $isGreeting ) { $this->expectException(AssertionFailedError::class); } $this->assertNotGreeting($input);}
Whole file
Below is a file with all the code I used for testing our custom PHPUnit assertion:
<?php namespace Tests\Unit; use PHPUnit\Framework\Assert;use PHPUnit\Framework\AssertionFailedError;use Tests\TestCase; class HelpersTest extends TestCase{ /** @test */ public function assert_string_is_a_greeting(): void { $stringA = 'Hello!'; $stringB = 'Bye!'; $this->assertGreeting($stringA); $this->assertNotGreeting($stringB); } protected function assertGreeting(string $potentialGreeting): void { Assert::assertStringContainsString('Hello', $potentialGreeting); } protected function assertNotGreeting(string $potentialGreeting): void { Assert::assertStringNotContainsString('Hello', $potentialGreeting); } /** * @test * @dataProvider greetingProvider */ public function can_correctly_assert_greetings(string $input, bool $isGreeting): void { if ( ! $isGreeting ) { $this->expectException(AssertionFailedError::class); } $this->assertGreeting($input); } /** * @test * @dataProvider greetingProvider */ public function can_correctly_assert_not_greetings(string $input, bool $isGreeting): void { if ( $isGreeting ) { $this->expectException(AssertionFailedError::class); } $this->assertNotGreeting($input); } protected function greetingProvider(): array { return [ ['Hello', true,], ['Hello there!', true,], ['Bye', false,], ['Bye bye', false,], ['See you later', false,], ]; }}
Conclusion
Testing your own helpers in PHPUnit is not that difficult. It comes down to catching an AssertionFailedError
exception. This is the perfect way to test your custom PHPUnit assertions!
Let me know if you've created a package with handy testing helpers and open-sourced it! I already created one such package for doing filesystem assertions. It it based on the Pest PHP testing framework, but it uses the same techniques as above. (Pest is a very nice wrapper around PHPUnit.)
Have fun, and, as always, if you have any questions or comments, please leave a comment below!⚡️
Published by Ralph J. Smit on in Testing . Last updated on 10 March 2022 .