Laravel Testing with PHPUnit and PEST: A Complete Beginner Guide

January 12, 2024 · 6 min read

2026 UPDATE - LARAVEL 13
We’re excited to announce that we have updated all of our blog post examples to reflect the new Laravel 13 version! Our previous examples were based on Laravel 10, but with the release of Laravel 13, we wanted to ensure that our readers have access to the most up-to-date information and examples.
Testing Your Laravel Application
Testing is a crucial step in ensuring that your application works as expected and remains stable over time. Laravel, being a framework that embraces testing, provides excellent support for writing tests with PHPUnit, and has also seen growing support for PEST, a testing framework with a focus on simplicity and elegance. In this article, we’ll explore how you can use PHPUnit and PEST to write tests for your Laravel app.
PEST: A Fresh Approach to Testing in Laravel
PEST is a testing framework that works on top of PHPUnit, offering a different approach with a focus on simplicity and a clean syntax. PEST allows you to write tests more expressively and elegantly.
Getting Started with PEST
To start using PEST in your Laravel blog, you’ll need to create the test with the --pest flag:
php artisan make:test UserTest --pest
Writing Your First PEST Test
After installing PEST, you can write more concise tests. Here’s the same test we wrote with PHPUnit, now with PEST:
namespace Tests\Feature;
use Tests\TestCase;
class PostTest extends TestCase
{
it('can access posts index page', function () {
$response = $this->get('/posts');
$response->assertStatus(200);
});
}To run your PEST tests, you can use the same commands as for PHPUnit tests:
php artisan test
Using PEST Features
PEST supports all PHPUnit assertions, but it also provides additional features and helpers that make testing more enjoyable. For example, you can use higher-order tests to reduce boilerplate:
namespace Tests\Feature;
use Tests\TestCase;
class PostTest extends TestCase
{
it('can access posts index page')->get('/posts')->assertStatus(200);
}
PHPUnit: The Foundation of Testing in Laravel
PHPUnit was the de facto standard for unit testing PHP applications. Laravel is built with testing in mind, and it includes out-of-the-box support for PHPUnit with a phpunit.xml configuration file and some base test classes.
Writing Your First PHPUnit Test
When you install Laravel, it comes with an example test file. To write a new test, you can create a file within the tests/Feature or tests/Unit directory, depending on the type of test you’re writing. Here’s an example of a feature test that checks if the blog posts index page is accessible:
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class PostTest extends TestCase
{
use RefreshDatabase;
/** @test */
public function posts_index_page_can_be_accessed()
{
$response = $this->get('/posts');
$response->assertStatus(200);
}
}Or we can simplify it with this:
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class PostTest extends TestCase
{
use RefreshDatabase;
/** @test */
public function posts_index_page_can_be_accessed()
{
$this->get('/posts')->assertStatus(200);
}
}You can run your tests using the following command:
php artisan test
Testing Database Interactions
Laravel provides traits like RefreshDatabase that you can use within your tests to reset your database after each test. This ensures that your tests are independent and won’t affect each other.
Here’s a test that ensures a new post can be created:
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
/** @test */
public function a_new_post_can_be_created()
{
$user = User::factory()->create(); // First we create a user
$postData = [
'title' => 'A New Post',
'content' => 'Content of the new post',
];
// This stimulates authentication when creating the new post
$this->actingAs($user)
->post('/posts', $postData);
$this->assertDatabaseHas('posts', [
'user_id' => $user->id,
'title' => 'A New Post',
'content' => 'Content of the new post',
]);
}
Feature Tests vs Unit Tests: When to Use Each
Laravel organizes tests into two directories:tests/Feature and tests/Unit. Understanding the difference shapes how you structure your entire test suite.
Unit tests test isolated logic with no database, no HTTP requests, and no framework bootstrapping. They are fast and granular, ideal for testing a single method or helper function in total isolation.
Feature tests test your application through the full request/response cycle, including routes, controllers, middleware, and the database. They confirm that your application actually behaves correctly end-to-end.
Rule of thumb for Laravel: Write mostly Feature tests. Laravel is a full-stack framework and the real value comes from testing how your routes, controllers, and models work together. Unit tests shine for standalone service classes, complex business logic calculations, or value objects that have no framework dependencies.
HTTP Testing: Testing Routes and Controllers
Laravel’s HTTP testing helpers let you simulate real HTTP requests against your application without a running browser or server. Laravel boots the full application internally and returns a test response you can inspect with assertions.
Testing GET Requests
// PEST syntax
it('shows the posts index page', function () {
$response = $this->get('/posts');
$response->assertStatus(200);
$response->assertSee('All Posts');
});
// PHPUnit syntax
public function test_posts_index_page_is_accessible(): void
{
$response = $this->get('/posts');
$response->assertStatus(200);
$response->assertSee('All Posts');
}Testing POST Requests
Use $this->post() to simulate form submissions. Pair it with database assertions to verify both the HTTP response and the resulting database state:
it('creates a new post', function () {
$user = User::factory()->create();
$response = $this->actingAs($user)->post('/posts', [
'title' => 'My First Post',
'content' => 'Some content here',
]);
$response->assertRedirect('/posts');
$this->assertDatabaseHas('posts', [
'title' => 'My First Post',
'user_id' => $user->id,
]);
});Testing Authenticated Routes with actingAs()
The actingAs() helper is one of the most-used methods in all of Laravel testing. It authenticates a user for the duration of a single test, so you can test protected routes without building a full login flow. Here is how to also verify that unauthenticated requests are correctly rejected:
it('requires authentication to create a post', function () {
// Unauthenticated request should redirect to login
$this->post('/posts', ['title' => 'Test'])
->assertRedirect('/login');
});
it('allows an authenticated user to create a post', function () {
$user = User::factory()->create();
$this->actingAs($user)
->post('/posts', [
'title' => 'Test Post',
'content' => 'Some content',
])
->assertRedirect('/posts');
});Using Model Factories for Test Data
Model factories generate fake but realistic database records for your tests. They ship with every Laravel application and live in database/factories. Without factories, database testing becomes tedious - you would need to manually insert raw SQL or use slow database seeders.
// Create and persist a user to the database
$user = User::factory()->create();
// Create without persisting (useful in unit tests)
$user = User::factory()->make();
// Create 5 posts belonging to a specific user
$posts = Post::factory()->count(5)->for($user)->create();
// Use a factory state (defined in your factory class)
$admin = User::factory()->admin()->create();
// Override specific attributes
$user = User::factory()->create(['email' => 'test@example.com']);
// Create a user with a related post in one call
$user = User::factory()->has(Post::factory()->count(3))->create();The RefreshDatabase trait resets the database between each test so factory-created records never bleed between tests. For larger test suites, LazilyRefreshDatabase is a faster alternative - it only runs migrations when a test actually touches the database, skipping the reset for tests that do not need it.
PEST vs PHPUnit: The Same Test, Two Syntaxes
PEST v3 ships as the default testing framework in new Laravel 11+ projects. Both frameworks are fully supported - PEST runs on top of PHPUnit internally. Here is the same Feature test written in both so you can compare the syntax directly:
// PHPUnit - class-based syntax
namespace Tests\Feature;
use App\Models\Post;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class PostTest extends TestCase
{
use RefreshDatabase;
public function test_authenticated_user_can_create_a_post(): void
{
$user = User::factory()->create();
$this->actingAs($user)
->post('/posts', [
'title' => 'My Post',
'content' => 'Post content',
])
->assertRedirect('/posts');
$this->assertDatabaseHas('posts', ['title' => 'My Post']);
}
}// PEST v3 - function-based syntax
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(Tests\TestCase::class, RefreshDatabase::class);
it('allows an authenticated user to create a post', function () {
$user = User::factory()->create();
$this->actingAs($user)
->post('/posts', [
'title' => 'My Post',
'content' => 'Post content',
])
->assertRedirect('/posts');
$this->assertDatabaseHas('posts', ['title' => 'My Post']);
});Choose PEST for new Laravel projects or when you prefer expressive, readable syntax. PEST v3 also adds architectural testing with arch() and mutation testing.
Stick with PHPUnit for existing codebases with established PHPUnit test suites, or when your team prefers class-based OOP style testing with maximum IDE tooling support.
Essential Laravel Test Assertions
These assertions cover the vast majority of what you will write in daily Laravel testing. All of them work identically in both PEST and PHPUnit:
// HTTP Response Assertions
$response->assertStatus(200); // specific HTTP status code
$response->assertOk(); // shorthand for assertStatus(200)
$response->assertNotFound(); // 404
$response->assertForbidden(); // 403
$response->assertRedirect('/path'); // response was a redirect
$response->assertSee('text'); // text appears in response HTML
$response->assertDontSee('text'); // text does not appear
$response->assertJson(['key' => 'val']); // response contains JSON data
$response->assertSessionHas('success'); // session has a flash key
// Database Assertions
$this->assertDatabaseHas('posts', ['title' => 'My Post']);
$this->assertDatabaseMissing('posts', ['title' => 'Deleted Post']);
$this->assertDatabaseCount('posts', 5);
$this->assertSoftDeleted('posts', ['id' => 1]);Running and Filtering Tests
# Run all tests
php artisan test
# Run only a specific test class
php artisan test --filter PostTest
# Run a single test by its description
php artisan test --filter "creates a new post"
# Stop immediately on the first failure
php artisan test --bail
# Run tests in parallel (significantly faster for large suites)
php artisan test --parallel
# Generate a code coverage report (requires Xdebug or PCOV)
php artisan test --coverageFor shared setup across tests within a PEST file, use beforeEach() instead of duplicating User::factory()->create() in every test:
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->user = User::factory()->create();
});
it('can view all posts', function () {
$this->actingAs($this->user)
->get('/posts')
->assertOk();
});
it('can create a post', function () {
$this->actingAs($this->user)
->post('/posts', ['title' => 'New Post', 'content' => 'Content'])
->assertRedirect('/posts');
});Conclusion
Testing is an integral part of the development cycle, and Laravel provides robust tools to help you ensure that your blog functions correctly. Whether you prefer the familiarity of PHPUnit or the expressive syntax of PEST, Laravel has got you covered.
With PHPUnit, you have a powerful and well-established testing framework at your disposal. If you’re looking for a more modern and elegant approach, PEST is an excellent choice that can make your tests even more readable and enjoyable to write.
Upcoming Articles in the Series
This article is part of our series Laravel for Beginners: A Step-by-Step Guide to Learn the Concepts.
Bring Your Ideas to Life 🚀
If you need help with a Laravel project let’s get in touch.
Lucky Media is proud to be recognized as a leading Laravel Development Agency
Technologies

Stay up-to-date
Be updated with all news, products and tips we share!
On this page
- 2026 UPDATE - LARAVEL 13
- Testing Your Laravel Application
- PEST: A Fresh Approach to Testing in Laravel
- PHPUnit: The Foundation of Testing in Laravel
- Feature Tests vs Unit Tests: When to Use Each
- HTTP Testing: Testing Routes and Controllers
- Using Model Factories for Test Data
- PEST vs PHPUnit: The Same Test, Two Syntaxes
- Essential Laravel Test Assertions
- Running and Filtering Tests
- Conclusion
- Upcoming Articles in the Series
- Bring Your Ideas to Life 🚀

