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
Leave a Reply