Creating a Feature Test in Laravel

Introduction

Testing is one of the most important disciplines in modern software development. As applications grow more complex and more users interact with them, ensuring reliability becomes essential. Laravel provides a robust testing ecosystem powered by PHPUnit and Laravel-specific helpers that allow developers to test their applications thoroughly. While unit tests focus on small, isolated pieces of logic, feature tests verify full user interactions, hitting routes, middleware, controllers, database operations, and responses—just like a real user would.

A feature test in Laravel simulates actual actions: logging in, submitting forms, accessing restricted pages, interacting with APIs, or performing CRUD operations. Because of this realism, feature tests help catch issues that might be missed by unit tests. They ensure entire workflows behave correctly and consistently throughout the application. Creating feature tests in Laravel is extremely straightforward. You can generate a test with a simple Artisan command:

php artisan make:test UserLoginTest

Inside the test, you can simulate actions like:

$this->post(‘/login’, [
’email’ => ‘[email protected]‘,
‘password’ => ‘password’
])->assertStatus(302);

This long-form post explores feature testing in great detail, covering what feature tests are, why they matter, how to generate them, how to structure them, how to test authentication, APIs, forms, CRUD operations, middleware, sessions, redirects, and more. By the end, you will understand how to build a solid test suite that guarantees your Laravel application remains stable and bug-free.

What Are Feature Tests?

Understanding the Concept

Feature tests in Laravel are designed to test entire features or user flows in your application. They do not focus on a single function or method. Instead, they replicate actual browser-level interactions. For example:

  • Submitting a login form
  • Creating a user
  • Updating an order
  • Fetching API data
  • Accessing protected routes
  • Interacting with middleware
  • Handling redirects

Feature tests simulate the environment of a real user interacting with your system.

How Feature Tests Differ From Unit Tests

Unit tests:

  • Test isolated methods
  • Do not interact with routes
  • Do not use real HTTP requests
  • Are very fast
  • Are ideal for pure logic

Feature tests:

  • Use full HTTP requests
  • Trigger middleware, routing, controllers
  • Hit database using Laravel’s testing database
  • Test behaviors and workflows
  • Ensure entire sections of the app work

Both are essential, but feature tests give greater confidence in the reliability of your full application.


Why Feature Tests Are Important

Ensuring Real User Flow Works

A feature test helps ensure that the app behaves correctly in real-world conditions. It detects issues like:

  • Broken routes
  • Incorrect form validation
  • Wrong redirect paths
  • Authentication failures
  • Missing middleware
  • Incorrect response codes
  • Database errors

Preventing Regression

As new features are added, feature tests prevent older features from breaking accidentally.

Documenting Behavior

Tests act as documentation for how routes and controllers should behave.

Faster Debugging

A feature test pinpoints the exact part of the workflow where something is wrong.

Confidence in Deployments

Developers can deploy new features confidently because tests confirm functionality.


Generating a Feature Test in Laravel

The Artisan Command

To create a new feature test:

php artisan make:test UserLoginTest

Laravel places the file inside:

tests/Feature/UserLoginTest.php

This file includes a ready-to-use test class.

Creating a Test With Unit Flag

To create a unit test instead:

php artisan make:test SomethingTest –unit

But for feature tests, the default behavior is correct.


Anatomy of a Feature Test

A basic feature test might look like:

public function test_user_can_login()
{
$response = $this->post(‘/login’, [
’email’ => ‘[email protected]‘,
‘password’ => ‘password’
]);

$response->assertStatus(302);

}

Key Elements Inside Feature Tests

HTTP Methods

You can simulate any HTTP request:

  • $this->get()
  • $this->post()
  • $this->put()
  • $this->patch()
  • $this->delete()
  • $this->json()

Assertions

Assertions verify expectations:

  • assertStatus()
  • assertRedirect()
  • assertSessionHas()
  • assertSee()
  • assertJson()
  • assertDatabaseHas()

Feature testing is powerful because you can combine multiple assertions in one workflow.


Feature Testing the Login Process

A very common feature test checks whether users can log in.

Step 1: Create a Test User

Laravel offers model factories:

$user = User::factory()->create([
‘password’ => bcrypt(‘password’)
]);

Step 2: Perform Login Request

$response = $this->post(‘/login’, [
’email’ => $user->email,
‘password’ => ‘password’
]);

$response->assertStatus(302);

This verifies login redirects successfully.

Step 3: Assert User Authentication

$this->assertAuthenticated();

You can also assert a specific user:

$this->assertAuthenticatedAs($user);


Testing Validation Errors

If a user submits invalid login data, form validation should fail:

$response = $this->post(‘/login’, [
’email’ => ”,
‘password’ => ”
]);

$response->assertSessionHasErrors([’email’, ‘password’]);

This ensures Laravel returns the correct errors.


Testing Redirects

You want to ensure successful login redirects to dashboard:

$response->assertRedirect(‘/dashboard’);

Redirect testing ensures correct navigation.


Testing Protected Routes

Protected routes require authentication.

$response = $this->get(‘/dashboard’);

$response->assertRedirect(‘/login’);

You can then log in and test again:

$this->actingAs($user);

$response = $this->get(‘/dashboard’);

$response->assertStatus(200);


Testing APIs in Feature Tests

Feature tests are perfect for API endpoints.

$response = $this->json(‘POST’, ‘/api/login’, [
’email’ => $user->email,
‘password’ => ‘password’
]);

$response->assertJsonStructure([‘token’]);

Testing API Authorization

$response = $this->getJson(‘/api/profile’);

$response->assertStatus(401);

Then authenticate:

$this->actingAs($user, ‘sanctum’);

$response = $this->getJson(‘/api/profile’);

$response->assertStatus(200);


Testing CRUD Operations

Create Operation

$response = $this->post(‘/posts’, [
‘title’ => ‘Demo’,
‘content’ => ‘Sample content’
]);

$response->assertStatus(302);

assertDatabaseHas(‘posts’, [
‘title’ => ‘Demo’
]);

Read Operation

$response = $this->get(‘/posts/1’);

$response->assertStatus(200);

Update Operation

$response = $this->put(‘/posts/1’, [
‘title’ => ‘Updated’
]);

assertDatabaseHas(‘posts’, [
‘title’ => ‘Updated’
]);

Delete Operation

$response = $this->delete(‘/posts/1’);

assertDatabaseMissing(‘posts’, [
‘id’ => 1
]);

Feature tests fully verify CRUD behavior.


Testing Middleware Behavior

Feature tests can validate middleware:

$response = $this->get(‘/admin’);

$response->assertRedirect(‘/login’);

actingAs:

$this->actingAs($user)->get(‘/admin’)->assertStatus(200);

This ensures authorization rules work.


Testing Session Data

$response = $this->post(‘/login’, [
’email’ => $user->email,
‘password’ => ‘password’
]);

$response->assertSessionHas(‘message’);

Sessions are common in forms and flash messages.


Testing Database State

Laravel includes several database assertions:

assertDatabaseHas
assertDatabaseMissing
assertSoftDeleted
assertNotSoftDeleted

Example:

assertDatabaseHas(‘users’, [’email’ => ‘[email protected]‘]);

Ensures data matches expectations.


Using RefreshDatabase Trait

Include:

use RefreshDatabase;

This resets the database between tests.
All migrations run before each test suite.


Testing File Uploads

Laravel tests support fake uploads:

Storage::fake(‘avatars’);

$file = UploadedFile::fake()->image(‘avatar.jpg’);

$this->post(‘/profile’, [
‘avatar’ => $file
]);

Storage::disk(‘avatars’)->assertExists(‘avatar.jpg’);


Testing Emails

Laravel tests can fake mail sending:

Mail::fake();

event(new UserRegistered($user));

Mail::assertSent(WelcomeEmail::class);


Testing Events

Event::fake();

event(new OrderPlaced());

Event::assertDispatched(OrderPlaced::class);


Testing Queued Jobs

Queue::fake();

dispatch(new SendInvoiceJob());

Queue::assertPushed(SendInvoiceJob::class);

Feature tests can verify all parts of the system.


Organizing Feature Tests

Organize by folder:

tests/Feature/Auth
tests/Feature/API
tests/Feature/Posts
tests/Feature/Admin

This structure helps maintainability in large applications.


Naming Conventions for Feature Tests

Good names:

UserCanLoginTest
AdminAccessTest
CreatePostFeatureTest
DeleteProductApiTest

Names should describe behaviors, not code.


Common Mistakes in Feature Testing

Forgetting to Use RefreshDatabase

This can cause unpredictable test failures.

Testing Too Much in One Method

Split into multiple focused tests.

Not Testing Validation

Validation is one of the most common failure points.

Not Testing Permissions

Apps often break due to inconsistent authorization.

Writing Feature Tests as Unit Tests

Feature tests should cover full flows, not isolated logic.


When to Use Feature Tests vs Unit Tests

Use Feature Tests When:

  • Testing controllers
  • Testing routes
  • Testing forms
  • Testing full workflows
  • Testing database operations
  • Testing middleware
  • Testing APIs

Use Unit Tests When:

  • Testing pure logic
  • Testing services
  • Testing helpers
  • Testing custom classes

Both are important for full coverage.


Example: Full Feature Test for Login

public function test_user_can_login_with_valid_credentials()
{
$user = User::factory()->create([
‘password’ => bcrypt(‘password’),
]);

$response = $this->post('/login', [
'email' => $user->email,
'password' => 'password',
]); $response->assertStatus(302); $this->assertAuthenticatedAs($user);

}

This fully tests login, redirects, and authentication.


Example: Feature Test for API Authentication

public function test_user_receives_token_on_valid_login()
{
$user = User::factory()->create([
‘password’ => bcrypt(‘secret123’),
]);

$response = $this->postJson('/api/login', [
'email' => $user->email,
'password' => 'secret123'
]); $response->assertStatus(200)
     ->assertJsonStructure(['token']);

}

This ensures token authentication works.


Improving Feature Tests in Large Applications

Use Helper Methods

Extract repeated logic:

public function loginUser()
{
return User::factory()->create();
}

Use Database Transactions

Faster than migrations.

Use Factories

Always generate clean sample data.

Use Seeders

Some tests require real-world data.


Running Feature Tests

Run all tests:

php artisan test

Run a specific test file:

php artisan test tests/Feature/UserLoginTest.php

Run a specific method:

php artisan test –filter=test_user_can_login

Testing is fast and optimized.


Improving Test Speed

Use In-Memory SQLite

Set database connection to sqlite memory for faster tests.

Minimize heavy operations

Avoid unnecessary file operations.

Disable debug logs during tests

Speeds up execution.


Feature Tests as Documentation

Feature tests show:

  • What endpoints exist
  • What data they require
  • How they behave
  • Validations
  • Permissions
  • Expected responses

They are self-updating documentation for your team.


Advanced Feature Testing Techniques

Acting As Different Users

Simulate roles:

$this->actingAs($admin);

Using Passport or Sanctum in Tests

Authenticate API requests for secure routes.

Testing Policies

$this->actingAs($user)->get(‘/admin’)->assertForbidden();

Testing Blade Views

$response->assertSee(‘Welcome’);


Importance of Feature Tests in Production-Ready Apps

A professional Laravel application must have feature tests for:

  • Authentication
  • Authorization
  • CRUD
  • API endpoints
  • Middleware
  • Form validation
  • Errors and exceptions
  • Email flows
  • Background jobs

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *