Laravel 5.5, testing, and you
Tags: laravel, php, testing
Published: 2017-12-17 19:47:55

---
title: Laravel 5.5, testing, and you
published: true
description: Improve your Laravel tests with some tips and tricks and increase your code quality.
tags: laravel, php, testing
---

If you're a programmer, chances are you love writing code. It's great and makes you feel awesome (at least when it's working!). Every line of code translates into more progress into your current task. However, that same thought could dissuade you from a very important step in building quality software: writing tests.

As a Laravel fanboy (if you use PHP and never heard of it, check it out - seriously), I can't help but notice how awesome it is to write tests with it right out of the box. However, we need to do it with responsability: maintaining the high quality of your project's code **means your test code should also be good**.

With that in mind and with some new tricks that Laravel 5.5 ships with, I decided to compile a few tips on how to improve your test code quality to help you keep your project bug-free and awesome. So let's get to it!


## 1. Use setUp() and tearDown()

I can't stress this enough. We all know that duplicate code is (usually) bad code, and this definitely applies to test code as well. Let's take a simple example where we test a social network application:

```PHP
<?php

class ExampleTest extends TestCase {
use DatabaseTransactions;

    /** @test */
    public function users_can_follow_each_other() {
        $user1 = factory(User::class)->create();
        $user2 = factory(User::class)->create();

        $this->actingAs($user1);

        $this->post('/follow', [
            'user_id' => $user2->id
        ]);

        $this->assertTrue($user2->isFollowedBy($user1));
    }

    /** @test */
    public function users_can_message_each_other() {
        $user1 = factory(User::class)->create();
        $user2 = factory(User::class)->create();

        $this->actingAs($user1);

        $this->post('/messages', [
            'user_id' => $user2->id
        ]);

        $this->assertDatabaseHas('messages', [
            'from' => $user1->id,
            'to' => $user2->id
        ]);
    }
}
```

Note that we are creating fresh new users every single test. We could just set up two and reuse them. And here comes the `setUp()` method to save us:

```PHP
<?php

class ExampleTest extends TestCase {
    use DatabaseTransactions;

    private $user1;
    private $user2;

    protected function setUp() {
        parent::setUp(); // this is important!

        $this->user1 = factory(User::class)->create();
        $this->user2 = factory(User::class)->create();
        $this->actingAs($this->user1);
    }

    /** @test */
    public function users_can_follow_each_other() {
        $this->post('/follow', [
            'user_id' => $this->user2->id
        ]);

        $this->assertTrue($this->user2->isFollowedBy($this->user1));
    }

    /** @test */
    public function users_can_message_each_other() {
        $this->post('/messages', [
            'user_id' => $this->user2->id
        ]);

        $this->assertDatabaseHas('messages', [
            'from' => $this->user1->id,
            'to' => $this->user2->id
        ]);
    }
}
```

Notice how much better and to the point the tests are now. All we had to do is override the `setUp()` method and create our users there, then assigning them as class attributes. We even tossed the `actingAs` method there because it made more sense to that test class. Just don't forget to add the `parent::setUp()` to the beginning of the `setUp()` method.

(pro tip: if you're using PhpStorm, hit `CTRL+O` to quickly override methods).

Now, every time any of the tests in this class run, the `setUp()` method will be called first, giving us fresh new users to play with. Pretty great. Can you imagine if we had a dozen methods that use users or that require a more complex set up? This will save a ton of your time when refactoring or incrementing your test code.

The `tearDown()` method can also be used in the same way, but it runs right after  your test ends. This is useful if you want to undo something, for example.

## 2. Use Carbon for time-sensitive scenarios

[Carbon is awesome](http://carbon.nesbot.com/docs/). Dealing with time can be a little annoying with PHP's native classes, but Carbon gives us a nice and intuitive syntax for all your date/time needs.

If you have time-sensitive scenarios (e.g you want a different behavior if a User is 10 days old), testing that might sound hard. A mediocre approach would be injecting the current time in the specific method you're trying to test, but that's annoying.

Fortunately, Carbon ships with a cool method to mock its `now()` method, essentially faking the current time for your tests!

```PHP
<?php

/** @test */
    public function only_10_days_old_users_can_upvote_a_post() {
        $user = factory(User::class)->create();
        $post = factory(Post::class)->create();

        $result = $user->likePost($post);
        $this->assertFalse($result);
        
        $tenDaysInTheFuture = Carbon::now()->addDays(10);
        Carbon::setTestNow($tenDaysInTheFuture);

        $result = $user->likePost($post);
        $this->assertTrue($result);
    }
```

The trick here is to use `Carbon::setTestNow` by passing the desired date to mock (which is also a Carbon object!).

**A very important warning:** this will also mock the current time for ALL future Carbon objects, which may cause problems when you're running all the tests in a single batch. To avoid that, usetip #1 to "cancel" the mock with the `tearDown()` method:

```PHP
<?php

protected function tearDown() {
        parent::tearDown();

        Carbon::setTestNow(); // this will clear the mock!
    }
```

## 3. Be smart with your assertions

Try to be as semantic as possible with your assertions. They come in different shapes for a reason and will give a better failure message when your test inevitably fails.

Of course you can just always use `$this->assertTrue`, but this will make your test less readable. Let's refactor an example:

```PHP
<?php

$group->addUsers([$user1, $user2]);
        $group->save();

        $this->assertTrue($group->users->count() === 2);
```

Sure it works, but why not have a more semantic approach with `$this->assertCount` or even `$this->assertContains`?

```PHP
<?php

$group->addUsers([$user1, $user2]);
        $group->save();

        $this->assertCount(2, $group->users->toArray());
```

That's better.

Remember you have dozens of assertions, so try picking the best one for your test scenario. Want to see if something was recorded or not in the database? `$this->assertDatabaseHas()` and `$this->assertDatabaseMissing()` are your friends.

Also, remember that when you're testing HTTP routes (using `$this->post`, for example), it's going to return a result object. That object also has its own assertions, like `$response->assertRedirect` and `$response->assertSessionHasErrors`.

One final tip: avoid complex logical assertions! Never assert that `$this->assertTrue(!$condition)`. That's basically saying "Let's assert that it is true that `$condition` is false". Using `$this->assertFalse($condition)` reads much better. Your team will appreciate :)


## 4. Exceptions: handle them (or don't)

Up to 5.4, Laravel had a bit of an issue with exception handling in tests - they were ignored, which caused way too many false negative tests in my experience. Fortunately, Laravel 5.5 has a new trick to solve this problem.

Sometimes you want PHP to throw exceptions at your code, sometimes you don't. When you're trying to test the middleware of, say, a route that requires you to be logged in, but you're trying to force your way as a guest, you might want to use `$this->expectException` to check if an AuthenticationException is thrown.

```PHP
<?php

/** @test */
    public function guest_cant_see_skills() {
        $this->expectException(AuthenticationException::class);
        
        $response = $this->get('/skills');
        $response->assertRedirect('/login');
    }
```

In Laravel 5.5, this is going to return `Failed asserting that exception of type "Illuminate\Auth\AuthenticationException" is thrown`, thus, a failure. This happens because laravel automatically "handles" the exception behind the scenes and never throws it on our tests. To handle this ourselves, we need to use one of L5.5's new methods:

```PHP
<?php

    /** @test */
    public function guest_cant_see_skills() {
        $this->expectException(AuthenticationException::class);
        $this->withoutExceptionHandling(); // <-- this!

        $response = $this->get('/skills');
        $response->assertRedirect('/login');
    }
```

Now it's going to run beautifully. You can also re-enable exception handling at any time using $this->withExceptionHandling().


## 5. Hidden validation errors are a problem

Whenever you run HTTP tests on routes with validation, make sure to properly assert that the response came without errors.

```PHP
<?php

/** @test */
    public function guest_cant_see_skills() {
        $user = factory(User::class)->create();
        $this->actingAs($user);

        $response = $this->post('/skills', [
            'name' => 'PHP',
            'proficiency' => 20,
        ]);

        $response->assertSessionMissing('errors');

        $this->assertTrue($user->hasSkill('PHP'));
    }
```

It might sound dumb to do that, especially because you KNOW you're passing the right request to your POST route, but believe me: as soon as the requirements change and your route validation rules change too, you might stare at your screen looking through your tests and trying to understand why they're not working (I know I did that in the past...) when in the end it was only because your post request wasn't valid anymore (say you add another required field to your forms).

I do wonder why laravel has a `assertSessionHasErrors()` method for HTTP requests, but not a `assertSessionDoesntHaveErrors()`. Got a better idea than using `assertSessionMissing('errors')`? Share with us in the comments :)

## 6. Mocks are your friends

If you're using Laravel's built-in solution for events, notifications and mail, testing them couldn't be easier. Laravel ships with helpers in its facades right out of the box so you don't have to mess with Mockery.

All you need to do is initiate with the `fake()` method (probably in your `setUp()`) and any mail, notification or event called will be "faked" and you can actually assert over the new mocks!

Here's a quick example using notifications:

```PHP
<?php

/** @test */
    public function guest_cant_see_skills() {
        Notification::fake();
        
        $user = factory(User::class)->create();
        $this->actingAs($user);

        $response = $this->post('/skills', [
            'name' => 'Laravel',
            'proficiency' => 85,
        ]);

        $response->assertSessionMissing('errors');
        $mentor = $user->mentor;

        Notification::assertSentTo([$mentor], StudentLearnedNewSkill::class);
    }
```

Laravel has a [sweet](https://laravel.com/docs/5.5/mocking) list of all the mocks available in its documentation, so check them out!

Well, that's it for now. I hope you learned something new :)

Got any more useful tips that you'd like to share or ideas on how to improve the topics I covered? Let me know in the comments - and follow me on [Twitter](twitter.com/edmilson_rocha_) ;)