Demystifying tests in Laravel

anwar_nairi

Anwar

Posted on December 10, 2022

Demystifying tests in Laravel

Hi and welcome for another article πŸ‘‹

This one will be a rapid-fire oriented post so that you can both see how easy it is to test your Laravel app, and you can refer to it whenever you want to remember how to test something.

Everytime you want to create a test, and the test class do not exist yet, just run this command.

php artisan make:test LoginTest
Enter fullscreen mode Exit fullscreen mode

And you run your tests using this command.

php artisan test
Enter fullscreen mode Exit fullscreen mode

Without further do, let's get into it!

Folder architecture suggestion

If you have a lot of tests, naming can be challenging. To not think of it, and if your test file is focused on a controller, model, ... you can just name the file the same as the entity you test.

your-app/
β”œβ”€β”€ app/
β”‚   β”œβ”€β”€ Http/
β”‚   β”‚   └── Controllers/
β”‚   β”‚       β”œβ”€β”€ LoginController.php
β”‚   β”‚       └── PostController.php
β”‚   β”œβ”€β”€ Models/
β”‚   β”‚   β”œβ”€β”€ Post.php
β”‚   β”‚   └── User.php
β”‚   β”œβ”€β”€ Rules/
β”‚   β”‚   └── BarCodeValid.php
β”‚   └── Helpers/
β”‚       └── Color.php
└── tests/
    β”œβ”€β”€ Feature/
    β”‚   β”œβ”€β”€ Http/
    β”‚   β”‚   └── Controllers/
    β”‚   β”‚       β”œβ”€β”€ LoginControllerTest.php
    β”‚   β”‚       └── PostControllerTest.php
    β”‚   β”œβ”€β”€ Models/
    β”‚   β”‚   β”œβ”€β”€ PostTest.php
    β”‚   β”‚   └── UserTest.php
    β”‚   └── Rules/
    β”‚       └── BarCodeValidTest.php
    └── Unit/
        └── Helpers/
            └── ColorTest.php
Enter fullscreen mode Exit fullscreen mode

1. Assert a route returns response without errors

namespace Tests\Feature;

use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;

class ContactUsControllerTest extends TestCase
{
  use WithFaker;

  public function testContactUsPageRendersWell()
  {
    $this
      ->get(route("contact-us.index"))
      ->assertOk();
  }
}
Enter fullscreen mode Exit fullscreen mode

1. Assert a user is logged

namespace Tests\Feature;

use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;

class LoginControllerTest extends TestCase
{
  use WithFaker;

  public function testUserIsLoggedAfterFormIsSubmitted()
  {
    $password = $this->faker->password();
    $user = User::factory()->create();

    $this->post(route("login"), [
        "email" => $user->email,
        "password" => $password,
      ])
      ->assertValid()
      ->assertRedirect(route("dashboard"));

    $this->assertAuthenticated();
    // or
    $this->assertAuthenticatedAs($user);
  }
}
Enter fullscreen mode Exit fullscreen mode

2. Assert an authenticated user can perform actions

namespace Tests\Feature;

use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;

class PostControllerTest extends TestCase
{
  use WithFaker;

  public function testAuthenticatedUserCanCreateBlogPost()
  {
    $user = User::factory()->create();

    $this->actingAs($user)
      ->post(route("post.store"), [
        "title" => $this->faker->sentence(),
        "content" => $this->faker->text(),
      ])
      ->assertValid();
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Assert a model has been saved after form submission

namespace Tests\Feature;

use App\Models\Item;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;

class ItemControllerTest extends TestCase
{
  use WithFaker;

  public function testItemSavedAfterUserSubmitsForm()
  {
    $user = User::factory()->create();
    $name = $this->faker->name();

    $this->assertDatabaseMissing(Item::class, [
      "name" => $name,
    ]);

    $this->actingAs($user)
      ->post(route("item.store"), [
        "name" => $name,
      ])
      ->assertValid();

    $this->assertDatabaseHas(Item::class, [
      "name" => $name,
    ]);
  }
}
Enter fullscreen mode Exit fullscreen mode

4. Assert a file has been uploaded

namespace Tests\Feature;

use App\Models\User;
use Illuminate\Http\UploadedFile;
use Tests\TestCase;

class SettingControllerTest extends TestCase
{
  public function testUserCanUploadItsProfilePicture()
  {
    $user = User::factory()->create();

    $file = UploadedFile::fake()->file("avatar.jpg");

    $this->assertDatabaseMissing(User::class, [
      "user_id" => $user->id,
      "profile_picture" => $file->hashName(),
    ]);

    $this->actingAs($user)
      ->post(route("setting.update"), [
        "profile_picture" => $file,
      ])
      ->assertValid();

    $this->assertDatabaseHas(User::class, [
      "user_id" => $user->id,
      "profile_picture" => $file->hashName(),
    ]);
  }
}
Enter fullscreen mode Exit fullscreen mode

5. Creating a fake model with relationships

namespace Tests\Feature;

use App\Models\Comment;
use App\Models\Post;
use App\Models\User;
use Tests\TestCase;

class PostControllerTest extends TestCase
{
  public function testAuthorCanEditPost()
  {
    $user = User::factory()->create();

    // Creates a post with 5 comments, each comments has 25 likes
    $post = Post::factory()
      ->for($user, "author")
      ->has(
        Comment::factory()
          ->has(Like::factory()->count(25))
          ->count(5)
      )
      ->create();
  }
}
Enter fullscreen mode Exit fullscreen mode

6. Assert that a session flash message is visible

namespace Tests\Feature;

use App\Models\Post;
use App\Models\User;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;

class PostControllerTest extends TestCase
{
  use WithFaker;

  public function testAuthorCanEditItsPost()
  {
    $user = User::factory()->create();
    $post = Post::factory()->for($user, "author")->create();

    $this->actingAs($user)
      ->post(route("post.update", $post), [
        "title" => $this->faker->name(),
      ])
      ->assertValid()
      ->assertSessionHas("success", "Post updated.");
  }
}
Enter fullscreen mode Exit fullscreen mode

7. Assert a validation error message is returned

namespace Tests\Feature;

use App\Models\Comment;
use App\Models\Post;
use App\Models\User;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;

class PostControllerTest extends TestCase
{
  use WithFaker;

  public function testAuthorSeeErrorIfEditingPostWithSameNameAsOneOfItsOtherPost()
  {
    $user = User::factory()->create();
    $post = Post::factory()->for($user, "author")->create();
    $name = $this->faker->name();

    Post::factory()
      ->for($user, "author")
      ->create([
        "title" => $name,
      ]);

    $this->actingAs($user)
      ->post(route("post.update", $post), [
        "title" => $name,
      ])
      ->assertInvalid([
        "title" => "The title has already been taken.",
      ]);
  }
}
Enter fullscreen mode Exit fullscreen mode

8. Assert an authorization

namespace Tests\Feature;

use App\Models\Post;
use App\Models\User;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;

class PostControllerTest extends TestCase
{
  use WithFaker;

  public function testUserCannotSeePostHeOrSheDidNotCreate()
  {
    $user = User::factory()->create();
    $otherUser = User::factory()->create();
    $post = Post::factory()->for($otherUser, "author")->create();

    $this->actingAs($user)
      ->get(route("post.show", $post))
      ->assertForbidden();
  }
}
Enter fullscreen mode Exit fullscreen mode

9. Assert a command ended without errors

namespace Tests\Feature;

use App\Models\User;
use Carbon\Carbon;
use Tests\TestCase;

class NotifyFreePlanEndsTest extends TestCase
{
  public function testUserCannotSeePostHeOrSheDidNotCreate()
  {
    $user = User::factory()
      ->count(15)
      ->create([
        "plan" => "free",
        "subscribed_at" => Carbon::now()->subDays(15),
      ]);

    $this->artisan("notify:free-plan-ends")
      ->assertSuccessful()
      ->expectsOutputToContain("15 users notified.");
  }
}
Enter fullscreen mode Exit fullscreen mode

11. Assert a json content is sent

namespace Tests\Feature;

use App\Models\User;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;

class TaskControllerTest extends TestCase
{
  use WithFaker;

  public function testAuthenticatedUserCanCreateTask()
  {
    $user = User::factory()->create();
    $name = $this->faker->name();
    $dueAt = $this->faker->date();

    $this->actingAs($user)
      ->post(route("task.store", [
        "name" => $name,
        "due_at" => $dueAt,
      ])
      ->assertJson([
        "name" => $name,
        "dueAt" => $dueAt,
      ]);
  }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

I hope this post gave you the motivation to start testing if you did not get used to it yet! When you feel ready, head on the Laravel Test documentation, this is a gold mine and you will find other awesome assertions.

Please also share your snippets in the comments section if you also have some quick and easy ways to test things in Laravel πŸ™

Happy testing πŸ§ͺ

πŸ’– πŸ’ͺ πŸ™… 🚩
anwar_nairi
Anwar

Posted on December 10, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related