DEV Community

Mary
Mary

Posted on

Pest-Driven Development: Literally The First Test

I'm blogging along with the laracasts series Pest Driven Laravel created by Christoph Rumpel as I work on a project I hope to open source. I'm just documenting how I'm working, the resources I'm using, the problems I run into and some cool things I'm reminded of as I develop.

The next step is to actually install pest. If I was truly practicing TDD, I would have done this when I started my laravel application because Test-Driven Development is a lifestyle with a history and a community I may as well take a moment to put my hand over my heart and remember out loud that I'm uncovering better ways of developing software. But that's okay, because pest works 'on top of' an existing repository, and when you install it a folder of tests is created.

> composer require pestphp/pest --dev --with-all-dependencies
> php artisan pest:install
> composer require pestphp/pest-plugin-laravel --dev
Enter fullscreen mode Exit fullscreen mode

Protip for my fellow beginners and tired people: definitely run these commands inside the directory that contains the app folder; it's not a global install. I also forgot to add the pest plugin and curly braces which threw me for an hour or so, so do remember that last line. We'll need it, and vs code is different than phpstorm.

Also, it's my personal opinion that when your terminal asks you to give a star to a repository on github that's going to save you hours of toil and heartache, actually taking the time to do it is the same thing as unlocking an achievement in real life. And then you can go back someday and see all those stars and realize how connected you are to all these people who are a lot like you and how much you learned from their hard work. But again, I digress.

Then you can open your /tests/Feature/pest.php file and check out your new pest file, which looks like this on my fresh install:

<?php

namespace Tests\Unit;

use PHPUnit\Framework\TestCase;

class ExampleTest extends TestCase
{
    /**
     * A basic test example.
     */
    public function test_that_true_is_true(): void
    {
        $this->assertTrue(true);
    }
}
Enter fullscreen mode Exit fullscreen mode

But the very first test in the course confirms that a request to a route returns a status code of 200. We'll take out all of the code in the file and replace it with

<?php

it('gives back successful response for the home page', function () {

    $response = $this->get('/');

    $response->assertStatus(200);
});

Enter fullscreen mode Exit fullscreen mode

So what's happening here? I think it's a fair time to stop and think about this question, especially because the code contains it and this.

Let's just talk about it for a minute.

From passing knowledge, to me it indicates Keyword-driven testing, which "separates the documentation of test cases โ€“ including both the data and functionality to use โ€“ from the prescription of the way the test cases are executed". Pest isn't the only option for testing, even in laravel, but I'm choosing it because its 'claim to fame' is allowing a developer to use less code to write tests and allow that code to be more readable. So it makes sense that people new to testing (me) are going to run into some unfamiliar keywords. The apperance of it combined with the extremely human-readable description as the first function parameter gave me pause. So I looked it up the pest source code, and got

if (! function_exists('it')) {
    /**
     * Adds the given closure as a test. The first argument
     * is the test description; the second argument is
     * a closure that contains the test expectations.
     *
     * @return Expectable|TestCall|TestCase|mixed
     */
    function it(string $description, Closure $closure = null): TestCall
    {
        $description = sprintf('it %s', $description);

        /** @var TestCall $test */
        $test = test($description, $closure);

        return $test;
    }
}
Enter fullscreen mode Exit fullscreen mode

It looks like it takes the description, adds it to the string 'it' and formats it to use it as a label for the callback we're passing in the second parameter. Then, when pest actually calls test() we have the super readable label and the function. test() has the same method description as it, but now our description is formatted for later and we can see things starting to really move inside the test() function (despite the syntax of the two function calls looking similar):

if (! function_exists('test')) {
    /**
     * Adds the given closure as a test. The first argument
     * is the test description; the second argument is
     * a closure that contains the test expectations.
     *
     * @return Expectable|TestCall|TestCase|mixed
     */
    function test(string $description = null, Closure $closure = null): HigherOrderTapProxy|TestCall
    {
        if ($description === null && TestSuite::getInstance()->test instanceof \PHPUnit\Framework\TestCase) {
            return new HigherOrderTapProxy(TestSuite::getInstance()->test);
        }

        $filename = Backtrace::testFile();

        return new TestCall(TestSuite::getInstance(), $filename, $description, $closure);
    }
}
Enter fullscreen mode Exit fullscreen mode

it and test mean so much in the context of testing they mean almost nothing, so I think this bit of new context is enough to give a little more meaning to what I'm reading.

What even is this?

I want to take a minute to dive into this, but I won't. Without fail, when I'm in a pairing situation and I ask a php dev what this is, I'm told we'll 'get to it later' and then we write actual code and that's probably a good thing. I know that this refers to the calling object, and there's always a moment when I'm reading or writing code in laravel as a beginner where I guess at what this is in a new context. This is no exception. I'm definitely going to come back to it in a later post, but if someone wants to have mercy and just tell me what I am calling get on (is it the whole app??) that would be great.

Some More Setup

Before I stepped back, I took a minute to create a pest alias. By default, you can run a test with ./vendor/bin/pest but I changed it to pest. If you're reading this and you haven't already-- don't forget to alias sudo to please.

Then I hopped into the phpunit.xml to uncomment:

<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
Enter fullscreen mode Exit fullscreen mode

which results in a super lightweight and separate testing database. And that reminded me of this one episode of No Compromises that I heard while driving to Nashville. Super hype for my first testing database.

Getting Pesty

The next part is to refactor the test code to make it more read more pest-y. First, we'll

  1. change $response = $this->get('/'); to get('/');. That requires use Pest\Laravel\get, but it looks much nicer.
  2. specify a named route using the route() helper instead of passing the string /. Now we have get(route('home')).
  3. remove $response->assertStatus(200); and call assertStatus(200) passing the 200 OK status code as a parameter. Now the entire file looks like:
<?php

use function Pest\Laravel\{get};

it('gives back successful response for the home page', function () {

    get(route('home'))->assertStatus(200);
});
Enter fullscreen mode Exit fullscreen mode

Another tangent: I listened to APIs, with Jess Archer on The Laravel Podcast today and there's a ton of useful information about returning HTTP status codes and why being specific about them is useful, and I'm really looking forward to seeing what can be done with that and pest.

Since I'm using jetstream, I saw a lot of output. They're listed by path alphabetically. But check it out: it does gives back successful response for the home page. Feels good, man.

A screenshot of pest output with passing tests and warnings listed

Top comments (3)

Collapse
 
robertobutti profile image
Roberto B.

Thank you for sharing.
Amazing post. I like so much also that you provided suggestions based on your "mistakes" and experiences like:

"I also forgot to add the pest plugin and curly braces which threw me for an hour or so, so do remember that last line"

Collapse
 
sifrious profile image
Mary

Thank you! If you like blog posts about making mistakes, you'll LOVE #3.

Also: I like your hat.

Collapse
 
surajoliver profile image
suraj o

This command needs php 7.3 doesn't work in 8.2 - composer require pestphp/pest