Lesson 3
Testing Your Laravel Application
Testing Your Laravel Application

We have secured and structured our Laravel application, building a robust foundation with authentication and middleware. Now it's time to ensure the reliability and quality of our application through testing. In this lesson, we will focus on the importance of automated testing in Laravel, teaching you how to write tests that validate the behavior of your application. This step is crucial in maintaining a sustainable codebase as your application grows.

What You'll Learn

In this section, you will learn how to create both unit and feature tests in Laravel. Testing ensures that your application behaves as expected, reducing the risk of bugs and improving code quality.

We'll discuss two types of tests:

  1. Feature Tests: These tests simulate user interactions with your application, verifying that the application responds correctly. Feature tests are essential for ensuring that your application's UI behaves as expected. We'll create a TodoControllerTest to demonstrate this.
  2. Unit Tests: These tests validate the functionality of individual components, such as services or models. Unit tests help you verify that each part of your application works correctly in isolation. We'll write tests for the TodoService to illustrate this.

Let's jump into the implementation!

Implementing Feature Tests
php
1<?php 2 3namespace Tests\Feature; 4 5use Tests\TestCase; 6use App\Models\Todo; 7use Illuminate\Foundation\Testing\RefreshDatabase; 8 9class TodoControllerTest extends TestCase 10{ 11 use RefreshDatabase; 12 13 public function setUp(): void 14 { 15 parent::setUp(); 16 $this->withoutExceptionHandling(); 17 } 18 19 public function test_index() 20 { 21 Todo::factory()->create(['title' => 'Test Todo1']); 22 Todo::factory()->create(['title' => 'Test Todo2']); 23 24 $response = $this->get('/todos'); 25 26 $response->assertStatus(200) 27 ->assertSee('Test Todo1') 28 ->assertSee('Test Todo2'); 29 } 30 31 public function test_store() 32 { 33 $response = $this->post('/todos', [ 34 'title' => 'New Todo', 35 'description' => 'New Description', 36 'completed' => false, 37 ]); 38 39 $response->assertRedirect('/todos'); 40 $this->assertDatabaseHas('todos', ['title' => 'New Todo']); 41 } 42 43 public function test_show() 44 { 45 $todo = Todo::factory()->create(); 46 47 $response = $this->get("/todos/{$todo->id}"); 48 49 $response->assertStatus(200) 50 ->assertSee($todo->title) 51 ->assertSee($todo->description); 52 } 53 54 public function test_update() 55 { 56 $todo = Todo::factory()->create(); 57 58 $response = $this->put("/todos/{$todo->id}", [ 59 'title' => 'Updated Title', 60 'description' => 'Updated Description', 61 'completed' => true, 62 ]); 63 64 $response->assertRedirect('/todos'); 65 $this->assertDatabaseHas('todos', ['title' => 'Updated Title']); 66 } 67 68 public function test_destroy() 69 { 70 $todo = Todo::factory()->create(); 71 72 $response = $this->delete("/todos/{$todo->id}"); 73 74 $response->assertRedirect('/todos'); 75 $this->assertDatabaseMissing('todos', ['id' => $todo->id]); 76 } 77}

Let's examine each test method:

  1. test_index: This method tests the index action of the TodoController. It creates two Todo instances, sends a GET request to /todos, and asserts that the response contains the titles of the created todos.
  2. test_store: This method tests the store action of the TodoController. It sends a POST request to /todos with a new todo's data, asserts that the response redirects to /todos, and verifies that the new todo is stored in the database.
  3. test_show: This method tests the show action of the TodoController. It creates a Todo instance, sends a GET request to /todos/{id}, and asserts that the response contains the todo's title and description.
  4. test_update: This method tests the update action of the TodoController. It creates a Todo instance, sends a PUT request to /todos/{id} with updated data, asserts that the response redirects to /todos, and verifies that the todo's title is updated in the database.
  5. test_destroy: This method tests the destroy action of the TodoController. It creates a Todo instance, sends a DELETE request to /todos/{id}, asserts that the response redirects to /todos, and verifies that the todo is removed from the database.
  6. setUp: This method is called before each test method. It calls the parent setUp method and disables exception handling to display detailed error messages.
  7. use RefreshDatabase: This trait resets the database after each test method, ensuring a clean state for each test.

By running these tests, you can verify that your TodoController behaves as expected, ensuring that your application's core functionality is working correctly.

Implementing Unit Tests
php
1<?php 2 3namespace Tests\Unit; 4 5use Tests\TestCase; 6use App\Services\TodoService; 7use App\Models\Todo; 8use Illuminate\Foundation\Testing\RefreshDatabase; 9 10class TodoServiceTest extends TestCase 11{ 12 use RefreshDatabase; 13 14 public function test_create() 15 { 16 $service = new TodoService(); 17 $result = $service->create('Test Todo', 'Test Desc', false); 18 19 $this->assertDatabaseHas('todos', ['title' => 'Test Todo']); 20 $this->assertEquals('Test Todo', $result->title); 21 $this->assertEquals('Test Desc', $result->description); 22 $this->assertFalse($result->completed); 23 } 24 25 public function test_find_all() 26 { 27 Todo::factory()->count(2)->create(); 28 29 $service = new TodoService(); 30 $result = $service->findAll(); 31 32 $this->assertCount(2, $result); 33 } 34 35 public function test_update() 36 { 37 $todo = Todo::factory()->create([ 38 'title' => 'Old Title', 39 'description' => 'Old Desc', 40 'completed' => false, 41 ]); 42 43 $service = new TodoService(); 44 $result = $service->update($todo->id, 'New Title', 'New Desc', true); 45 46 $this->assertTrue($result); 47 $this->assertDatabaseHas('todos', ['title' => 'New Title', 'completed' => true]); 48 } 49 50 public function test_delete() 51 { 52 $todo = Todo::factory()->create(); 53 54 $service = new TodoService(); 55 $result = $service->delete($todo->id); 56 57 $this->assertTrue($result == 1); 58 $this->assertDatabaseMissing('todos', ['id' => $todo->id]); 59 } 60}

Let's break down each test method:

  1. test_create: This method tests the create method of the TodoService. It creates a new todo, verifies that it is stored in the database, and checks that the returned todo matches the input data.
  2. test_find_all: This method tests the findAll method of the TodoService. It creates two todos, retrieves all todos using the service, and asserts that the result contains two todos.
  3. test_update: This method tests the update method of the TodoService. It creates a todo, updates its data, and verifies that the todo is updated in the database.
  4. test_delete: This method tests the delete method of the TodoService. It creates a todo, deletes it using the service, and asserts that the todo is removed from the database.

To run your tests, you need to go to the project root directory and execute the following command:

Bash
1phpunit

This command will run all the tests in your Laravel application, providing you with detailed feedback on the test results. By writing and running tests, you can ensure that your application behaves as expected and catches bugs early in the development process.

Why It Matters

Testing is a cornerstone of professional software development. It ensures your code is functioning correctly and helps catch bugs early in the development process. By integrating testing into your workflow, you build a more reliable and maintainable application. In the long run, effective testing saves time and resources, allowing you to expand your application with confidence. Are you ready to ensure your Laravel application is rock-solid? Let's dive into the practice section and start testing together.

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.