How to test custom PHPUnit assertions & helpers | RJS

How to test custom PHPUnit assertions & helpers

Ralph J. Smit Laravel & PHP-developer.

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:

  1. Input a few things that should pass, and do nothing. The helper should never fail, so the tests will be green.

  2. 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.


Hey!👋 Like what you read?

Every now and then I send out a newsletter with new articles, tutorials and packages, mainly related to PHP in general and Laravel.

Subscribe to the newsletter and stay up to date with all things Laravel, Tailwind, WordPress & PHP. Never miss an update.🙋‍