Skip to content

Month: March 2017

Extending Laravel’s Functional Tests with Macros

I was fiddling around with a new project and I wanted to add a quick functional test to make sure that entries were appearing in the order that I expected. I know that neither Laravel nor PHPUnit have an assertion for that built in, but I figured it would be no problem to add it to the base TestCase class.

Bzzzt, sorry, nope, thanks for playing.

In the pre-Laravel 5.4 days, I would have created a function called seeThisBeforeThat() on the TestCase class and been all set.

$this->visit(‘/entries/’)->seeThisBeforeThat(‘Entry 1’. ‘Entry 4’);

With 5.4, though, we mostly don’t test assertions against the TestCase class, we test them against the Response that’s returned when we get() or post().

OK, no sweat, I’ll just extend the TestResponse class to add an extra assertion. (Did you know that when you’re testing, the Response that you get back isn’t a standard Illuminate\Http\Response? The Response is wrapped in a class called TestResponse which is where the assertions live.) Extending the TestResponse class will work as long as the Response is instantiated through the IOC container and not … oh, it’s instantiated directly.

Stuck, right? Wrong.

A recent blog post (https://unnikked.ga/understanding-the-laravel-macroable-trait-dab051f09172#.lcxjul9oz) reminded me that lots of Laravel classes implement the Macroable trait and TestResponse is one of them. Macros, for those who haven’t used them, allow you to add functions to existing classes without making a new class. (The Laravel docs are a little spotty on macros – https://laravel.com/docs/5.4/responses#response-macros is all there is and it doesn’t explain how widely available macros are. Extending those docs could be a good first PR for someone.)

It’s precisely what I need in a case where I can’t inject an extended TestResponse class but want to add a quick function to it.

Here’s what I did:

In AppServiceProvider, I added the following code:

public function register()
    {
        if ($this->app->environment('testing')) {
            $this->addTestResponseMacros();
        }
    }

    private function addTestResponseMacros()
    {
        TestResponse::macro('assertThisBeforeThat', function($earlier, $later) {
            // since we want this to fail if either isn't there, assert that we see stuff.
            $this->assertSee($earlier);
            $this->assertSee($later);
            $body = $this->getContent();
            return PHPUnit::assertGreaterThan(
                strpos($body, $earlier),  
                strpos($body, $later),
                "{$earlier} does not appear before {$later}"
            );
        });
    }

And also … oh, wait, no, that’s it.

The test is pretty straightforward:

/** @test */
    public function guest_can_view_entry_list ()
    {
        // ... setup code to make a list of entries

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

        $response->assertStatus(200)
            ->assertThisBeforeThat('Entry 1', 'Entry 4');
    }

Is there something else you want to assert() about the response? Just make a macro.

1 Comment