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.
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:
- 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.
- 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!
php1<?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:
- test_index: This method tests the
index
action of theTodoController
. It creates twoTodo
instances, sends aGET
request to/todos
, and asserts that the response contains the titles of the created todos. - test_store: This method tests the
store
action of theTodoController
. It sends aPOST
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. - test_show: This method tests the
show
action of theTodoController
. It creates aTodo
instance, sends aGET
request to/todos/{id}
, and asserts that the response contains the todo's title and description. - test_update: This method tests the
update
action of theTodoController
. It creates aTodo
instance, sends aPUT
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. - test_destroy: This method tests the
destroy
action of theTodoController
. It creates aTodo
instance, sends aDELETE
request to/todos/{id}
, asserts that the response redirects to/todos
, and verifies that the todo is removed from the database. - setUp: This method is called before each test method. It calls the parent
setUp
method and disables exception handling to display detailed error messages. - 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.
php1<?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:
- test_create: This method tests the
create
method of theTodoService
. It creates a new todo, verifies that it is stored in the database, and checks that the returned todo matches the input data. - test_find_all: This method tests the
findAll
method of theTodoService
. It creates two todos, retrieves all todos using the service, and asserts that the result contains two todos. - test_update: This method tests the
update
method of theTodoService
. It creates a todo, updates its data, and verifies that the todo is updated in the database. - test_delete: This method tests the
delete
method of theTodoService
. 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:
Bash1phpunit
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.
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.