php[architect] logo

Want to check out an issue? Sign up to receive a special offer.

Getting Started With the Pest Testing Framework

Posted by on October 19, 2023

As a practitioner of test-driven development, I’m always looking for different ways to write test code faster. Sometimes, this comes as little things like learning a faster way to run my tests using a built-in editor setting, but sometimes it’s a whole new way to write my tests.

In this article, we’re going to discuss the Pest testing framework, including how to install it, how to configure it, and some important tips to help you run your tests faster.

What is Pest?

Pest is a testing framework for PHP, and according to their website, it’s built “with a focus on simplicity, meticulously designed to bring back the joy of testing in PHP” and it’s hard to disagree with that statement. Pest tests are easy to read and understand because the code syntax closely resembles natural language.

Pest is built on top of PHPUnit but it adds a lot of nice improvements inspired by Ruby’s Rspec and Jest libraries to make it a completely different experience writing and running tests. Part of that is the way it changes the structure of the tests so they’re built using a series of anonymous functions that force us to make tests that are easier to read and part of it is the command line options and how nice the output display is.

Why Use Pest?

At a high level, Pest provides a fluent interface for writing automated tests with the use of its Expectation API (which will talk about in a little bit) and a nice interface for seeing the output of our tests’ results, including concise error messages and easy-to-read stack tracks which will make debugging fast and effortless.

It also includes some beneficial additional features such as built-in parallel testing, coverage, watch mode, architecture testing, native profiling tools, and snapshot testing.

Because it’s built on top of PHPUnit we don’t need to rewrite all of our PHPUnit tests for Pest immediately and can instead use Pest to run our existing PHPUnit-based tests automatically. There are also migration tools to migrate our tests but I haven’t tried any of them so I can’t vouch for the results. We do need to replace all of our calls to the phpunit command with the pest command as Pest prevents phpunit from running.

Installation

Installation as always is done using Composer.

composer require pestphp/pest --dev --with-all-dependencies

Then we also need to initialize Pest using the --init switch.

./vendor/bin/pest --init

This will create several files, including some example tests, the Pest.php configuration file used by Pest, and a phpunit.xml configuration file used by PHPUnit.

There’s also a wide variety of plugins or extensions for your IDE. It’s not required and for the remainder of this video I’ll be using the command line but I highly recommend installing a plugin for your IDE to speed up your development flow. Nothing beats the speed of running a single test or file using a keyboard shortcut.

We’ll discuss more after this word from our sponsors.

File Structure

Pest’s file structure for our tests puts all the test files inside a “tests” directory at the root of our project. In its default configuration, it creates a “Unit” directory for any tests that interact with a single test and a “Feature” directory that interacts with a set of classes together like a controller would do.

tests
    - Unit
        - ExampleTest.php
    - Feature
        - ExampleTest.php
    - Pest.php
phpunit.xml

Files need to end in the word “Test” for Pest to autoload them and run the files.

We can also put tests inside directories. I like to set it up so we have test files that test common conditions so I might have a “User” directory with an “AdministratorTest.php” for tests that are all based on the “Administrator” role in my application.

Our First Test

Let’s open tests/Unit/ExampleTest.php and take a look at what we have so far:

<?php

test('example', function () {
    expect(true)->toBeTrue();
});

The test() function is where we create our test and it accepts two parameters. The first is a string to indicate what we’re testing and the second is an anonymous function that’s the actual test logic. Inside that function we have a very basic set of function calls that use Pest’s “Expectation API” to check to perform the testing.

I’m going to quickly alter the test so we can see how it works on a little more complicated version. I’m just going to give the test a better name to indicate what we’re testing and change toBeTrue() to toBeFalse().

test('false should be false', function () {
    expect(true)->toBeFalse();
});

Now we can run the test at the command line using:

./vendor/bin/pest tests/Unit/ExampleTest.php

When we run this command we’ll get a failing test which is what we expect because we’re expecting true to be false which is not true. This is what you’ll see when you make a change that causes your tests to fail. I love how it’s showing the name of the test and exactly where it’s failing.

$ ./vendor/bin/pest tests/Unit/ExampleTest.php
 FAIL  Tests\Unit\ExampleTestfalse should be false                                           0.07s  
  ───────────────────────────────────────────────────────────────────────  
   FAILED  Tests\Unit\ExampleTest > false should be false                  
  Failed asserting that true is false.

  at tests/Unit/ExampleTest.php:4
      1▕ <?php
      23▕ test('false should be false', function () {
  ➜   4▕     expect(true)->toBeFalse();
      5▕ });

  1   tests/Unit/ExampleTest.php:4


  Tests:    1 failed (1 assertions)
  Duration: 0.32s

Let’s fix it so it passes.

test('false should be false', function () {
    expect(false)->toBeFalse();
});

Then we can run it and see it pass.

$ ./vendor/bin/pest tests/Unit/ExampleTest.php
   PASS  Tests\Unit\ExampleTest
  ✓ false should be false                                           0.03s  

  Tests:    1 passed (1 assertions)
  Duration: 0.46s

Note how it shows we passed correctly and we get that nice checkmark for the tests that pass.

Expectation API

In our previous examples, we used Pest’s expectation API to perform assertions in our test code. The expect() function is the core part of the expectation API and is used to assert that certain conditions have been met. The expectation API creates a fluent interface that allows us to concisely and easily test our code and explain exactly what it is we’re expecting to see come out of our tests.

For example, in our previous example, we used expect(false)->toBeFalse() to ensure that the value in our test is false. Pest’s expectation API provides a variety of other assertion functions that you can use to test the behavior of your code, such as toBe() to test any value, toBeTrue(), and toContain().

We can do something like the following with the expectation API to test that a value is an integer and 42.

$value = 42;
expect($value)
    ->toBeInt()
    ->toBe(42);

To add additional levels of checks we can assert things that it’s not as well such as not being a string

$value = 42;
expect($value)
    ->not
    ->toBeString(); // It's not a string

Assertion API

Because Pest is built on top of PHPUnit the expectation API is not the only way to perform assertions of our test code. We can also use PHPUnit’s assertion API which can be extremely useful if you’re already familiar with PHPUnit or if you need to perform more complicated assertions than what is available using the expectation API.

While Pest’s expectation API provides a convenient way to perform assertions, it’s not the only option available. You can also use PHPUnit’s assertion API, which can be useful if you’re already familiar with PHPUnit’s assertion API or if you need to perform more complex assertions that aren’t available in Pest’s expectation API.

test('false should be false', function () {
    $this->assertFalse(true);
});

Because we’re running the test with Pest we still get nice output.

$ ./vendor/bin/pest tests/Unit/ExampleTest.php

   FAIL  Tests\Unit\ExampleTest
  ⨯ false should be false                                           0.06s  
  ───────────────────────────────────────────────────────────────────────  
   FAILED  Tests\Unit\ExampleTest > false should be false                  
  Failed asserting that true is false.

  at tests/Unit/ExampleTest.php:4
      1▕ <?php
      23▕ test('false should be false', function () {
  ➜   4▕     $this->assertFalse(true);
      5▕ });

  1   tests/Unit/ExampleTest.php:4


  Tests:    1 failed (1 assertions)
  Duration: 0.35s

If you’re not aware of how to use PHPUnit’s assertion API, you can find the full documentation on the PHPUnit website.

Other Important Features

We’ve only touched the basics in this video (as it’s a getting started video) and we could go on but we wanted at least mention several features that will make your life so much easier.

These include:

  • Parallel features for faster test runs
  • Datasets for running the same test with different data
  • Native profiling tools to optimize slow-running tests
  • Out-of-the-box Architectural Testing to test application rules like making sure we don’t commit code with var_dumps
  • Coverage report directly on the terminal to track code coverage
  • Plugins, such as Watch Mode and Snapshot testing, to improve Pest even more

What You Need to Know

  • Pest is a testing framework built on top of PHPUnit
  • Uses expectation API to create a fluent interface for testing
  • Provides lots of features to speed up test development

Tags: ,
 

Leave a comment

Use the form below to leave a comment: