← Back to all tutorials

First Experience With PestPHP

First Experience With PestPHP

Today, in the latest session building the web front-end to the Raspberry PI Weather Station that I've been working on for the last month or two, I decided to introduce testing to the project.

This might seem strange to many, that I'd start building a web application yet introduce testing later. However, the project started off as a pretty small and trivial web application to support a 2 - 3 part tutorial series that I've been writing for Twilio. Given that, I never intended for it to be an application that had anything other than a short shelf life, nor one that took on a life of its own.

However, the more I worked on it, the more I became invested in the project and couldn't put it down. This was for a number of reasons, but primarily includd wanting to learn more about Tailwind CSS and the Slim Framework; my current two favourite tools of choice, when it comes to web development.

So, as can so often be the case, what was only going to be "a little app" has grown into something more. As a result, I felt it pretty important to add testing at the next available opportunity. That way, I could continue to develop it with confidence, knowing that it would work as expected for anyone who used or deployed it.

Given that, what testing framework should I use? For years, I've used PHPUnit, and in more recent years, I've increasingly turned to Codeception - especially I need to simulate user interaction or do database testing.

This time, however, I chose Pest PHP for the first time.

Being, primarily, a PHP developer, I couldn't help hearing about the framework over the last couple of months; so I'd been meaning to give it a try. Unfortunately, up until this point, I hadn't had a project to use it with, and I didn't want to just play around aimlessly.

If you're a PHP developer (you're reading this blog, so you likely are), I'm guessing that you've heard of it, or are using it. If you've not heard about it, or not heard much, Pest PHP seems to be all the rage in the Laravel community. Because of that, believe it or not, I avoided it, as I usually avoid things that "everyone" seems to be into.

That's not the most rational of mindsets, to be fair. I did the same with the Stieg Larsson series, and loved the books when I got into them!

I put the mindset to the side and, whipping out the ever trusty Composer, installed it into the project as a dev dependency, and started writing some tests with it.

The ups

For the most part, my experience (so far) has been very positive. The documentation on installing it and writing the initial tests was spot-on, so I got up and running in a trivial amount of time.

Despite not being used to the Pest-style of assertions, I started getting the hang of them in about 5 - 10 minutes. While different for me, Pest started to take on a pretty natural way of working after a short while. The tests seemed to let me be that much more expressive than I've been used to, which was a welcome change.

As I started exploring further, and referring back to the documentation, I appreciated that the documentation's structure was very intuitive, allowing me to find the applicable information rather quickly.

I didn't make much, if any, use of the in-built search functionality, but for what I did, it worked nicely. That it used the same keyboard shortcut as Tailwind CSS' built-in search - which I use heavily - was an excellent bonus.

Based on all of this, it's created a very positive impression in my mind.

The downs

However, it hasn't been without its drawbacks. To reiterate, I'm just getting started with it, so my impressions may change, soon, as my knowledge and experience continue to grow.

For now, two things stand out for me. The first one is the mocking functionality. While the documentation provides a few examples of how to use it, and, thoughtfully, links to the Mockery documentation, which the library wraps, if you need to go further, I found the second example misleading.

Take a look at the example below, which I lifted from the documentation.

test('some service', function () {
    $mock = mock(UserRepository::class)
        ->shouldReceive('save')
        ->once()
        ->andReturn(true);

    expect($mock->save('Nuno'))->toBeTrue();
});

While this is a nice, clean, example, when I tried to follow it, it didn't work, as the mock object, $mock, wasn't a true representation of the expected object. Given that, the class I was attempting to test couldn't be instantiated, as it was expecting a different object type.

Rather, after setting up the mock expectation, I had to call the expectation's getMock() or makePartial() methods to get the mock object to pass to the class' constructor, as you can see in the example below.

<?php

use Laminas\Diactoros\Response\RedirectResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use WeatherStation\Controller\WeatherDataSearchController;

beforeEach(function () {
    $this->request = mock(ServerRequestInterface::class);
    $this->response = mock(ResponseInterface::class);
    $this->controller = new WeatherDataSearchController();
});

test(
    'The weather data controller redirects to the results controller if the start date validates successfully', 
    function (string $startDate) {
        $request = $this->request
            ->shouldReceive('getParsedBody')
            ->once()
            ->andReturn([
                'startDate' => $startDate
            ])
            ->getMock();
        $result = $this
            ->controller
            ->__invoke(
                $request, 
                $this->response->makePartial(), 
                []
            );

        expect($result)
            ->toBeInstanceOf(RedirectResponse::class);

        expect($result->getHeaderLine('Location'))
            ->toEqual('/page1/2022-04-21/');
    }
)->with([
    ['2022-04-21'],
]);

This in't a deal-breaker. After reading through the relevant Mockery documentation, and several related classes, I figured out what I needed to do. However, it would be helpful to see the documentation better reflect the functionality with more complete examples.

Secondly, it felt a little strange to refer to Mockery's documentation to be able to use Pest PHP. I've been pondering whether to include this or not, as I don't want it to seem like a cheap shot or a petty critique. So I hope I am expressing myself appropriately, and that my intent is coming across as meant.

I'm far more used to working with a single documentation set when working with a given library; with some recent examples being several laminas packages, such as laminas-validator, laminas-servicemanager, and laminas-diactoros, and Mezzio.

This said, I appreciate that I've referred to Prophecy's documentation when working with PHPUnit. I'm not saying that there's anything wrong with it, but that it felt a little strange.

On the flip side, however, I appreciate that Pest PHP is built on top of PHPUnit and Mockery (perhaps other testing frameworks as well). Given that, it makes no sense to duplicate the underlying framework documentation. Rather, it makes far more sense, as they have done, to instead link to the respective documentation and only focus on Pest PHP-specific content.

I appreciate how it's done, why, and how it makes sense. I'm just pointing out my initial impressions with it.

In short

From my limited time with Pest PHP, I find it to be a nice, streamlined, well-thought-out testing framework, that doesn't, for the most part, take a lot to use. While there are a number of existing, high-quality testing frameworks available for PHP, I can see a place for Pest PHP among them. It won't be for everyone, but neither are any of the others, either.

I'd like the mock documentation to be a little more fleshed out, and accurate, and to that end, I intend to propose a PR and see if it's accepted. That's one of the beauties of open source software!

From the short time that I've spent with it, I don't know that I'm going to use it in favour of PHPUnit or Codeception anytime soon. But, it's left a favourable impression on me, so I could see it becoming part of my regular toolchain. What's more, I'm keen to see how the impression evolves while using it on my current project.

If you'd like to see my attempts with it, so far, check out the repository, and please leave my your, constructive, feedback.

Matthew Setter Matthew Setter is a long-time web developer, systems administrator, and technical writer. He's also the founder of Web Dev with Matt, the author of Mezzio Essentials and Deploy With Docker Compose, and the host of Free the Geek. Follow Matthew on Twitter and LinkedIn.
Posted in Software Development

Get extra tips and tricks direct to your inbox

Sign up to the Web Dev with Matt mailing list

You can unsubscribe at any time.