Pest PHP - Write Unit Tests in Style

Published 24/05/2020 | Last updated 17/12/2021

Not heard of Pest PHP? Here's why you want to.

Do you write unit tests in your PHP projects? If you do, you've likely heard of PhpUnit. Its basically the defacto testing library. It offers all kinds of methods for asserting that your code actually does what you think it does. In fact, it comes bundled in as default with Laravel.

To get us started, let's look at the example test written in Laravel when you first create a new project:

<?php
 
namespace Tests\Feature;
 
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
 
class ExampleTest extends TestCase
{
/**
* A basic test example.
*
* @return void
*/
public function testBasicTest()
{
$response = $this->get('/');
 
$response->assertStatus(200);
}
}

First thoughts? Wordy. Its all very verbose. Even if we strip whitespace and comments, we're still at 12 lines of code. But we're only actually interested in 3 of them: public function testBasicTest(), $response = $this->get('/'); and $response->assertStatus(200);.

Wouldn't it be nice to strip out all that boilerplate code and end up with just the code we're interested in? Yes. Yes, it would. Well, we're not the only ones who think so. @enunomaduro thinks so too. If you've never heard of him, I might suggest you aquaint yourself. He's fairly well known in the Laravel community, and he's the brain behind Laravel Zero.

However, unlike you and I, he decided to do something about it. The result? Pest PHP. Please allow me the honour of showing you that same test from earlier, written in Pest:

<?php
 
it('works')->get('/')->assertStatus(200);

"Wait, what?!? Shut up. Stop it. What are you smoking?", I hear you say. "My lord, is that... legal?", I hear you heckle. Not only did we get rid of all the boilerplate, but we were actually able to reduce the entire test to a single line. That in of itself is enough to make Pest PHP worth your time.

How does it work?

So, what's actually going on here? How are we able to get rid of all the boilerplate, whilst maintaining the same level of functionality?

Warning: what follows is GREATLY simplified and you should not in anyway feel you know the complete inner workings of PEST after reading the next few paragraphs.

It starts with the global function it (there's also one called test). In fact, you can head to vendor/pestphp/pest/src/globals.php to see the source for yourself. Pest's composer.json takes care of autoloading that file for you, making it available for all your tests.

When you call that method, you pass a wonderful string description (no more underscores or camel case) to represent your test, and a closure.

Pest will return you a fresh new TestCall object, ready to meet the world and get to work (child labour laws are quite lax in Pest). That TestCall object is added to a static TestSuite instance, along with its siblings. That TestSuite is also used by the ./vendor/bin/pest command (which you call to actually run the test suite), which calls on a Command class that is itself a subclass of PhpUnit's Command (I hope you're still following, and not asleep at your keyboard).

Therein lies Pest's little secret to complete interoperability with PhpUnit. It is PhpUnit, with some sweet sweet sugar sprinkled on top to the point where you might as well call it a diabetic doughnut. In fact, all your "classic" PhpUnit tests will work out of the box with Pest! Pretty awesome, huh?

The Command class has a run method, which loops over each of your tests and, much the same as PhpUnit, throws an error if a test doesn't pass.

Now, obviously, there is more to it than that. Pest does a fair bit more work, including loading and running plugins (oh yeah, Pest is extendable!), handling datasets (similar to data providers in PhpUnit, but cleaner), and configuring code coverage if you've asked for it.

However, that brief overview should give you at least a hint into how calling it works. Remember, the answer is never "by magic".

How do I install Pest?

Pest PHP has some amazing documentation, so I'll defer to that: https://pestphp.com/docs/installation/

Here's the gist:

  1. Make sure you're using >= PHP 7.3
  2. Make sure you're using >= PhpUnit 9.0
  3. Make sure you're using >= nunomaduro/collision 5.0
  4. Install via composer: composer require pestphp/pest --dev

If you're using Laravel, you can also run php artisan pest:install to make setup even easier, and provide a few extra little goodies.

Once everything is installed, you're ready for showtime: ./vendor/bin/pest.

Should I use Pest?

I think so, yes. I'll certainly be adopting it going forwards, and I can't see any reason not to use it.

It runs just as quickly as native PhpUnit (or at least the difference is so small that it is imperceptible), saves you a ton of time writing boilerplate, and saves even more time when you or one of your coding pals comes back to read the test suite.

In conclusion, I think its a fab little testing tool that will only get better with time, just like your code!

As one famous disgusting microwavable hamburger company once put it: "What a time to be alive!"

Like what you see?

If you enjoy reading my content, please consider sponsoring me. I don't spend it on cups of coffee; it all goes towards freeing up more of my time to work on open source, tutorials and more posts like this one.