Key Takeaways
- Laravel provides built-in support for testing with Pest and PHPUnit.
- A good unit test should verify a single expected behavior. This makes it easier to understand and fix when it fails.
- Test your application logic with unit tests, not routes, databases, or the behavior of the Laravel framework.
Laravel is one of the most popular PHP frameworks for app development. Laravel applications can grow fast. One little change in a controller, service class, helper, or model method can break something else in the project. That’s why testing is crucial.
Laravel comes with built-in support for testing. You can write tests with Pest or PHPUnit, run them using Artisan, test small pieces of application logic, verify expected results with assertions, and use mocks to keep tests isolated from external services.
In this guide, we’ll explore what Laravel unit tests are, how they differ from feature tests, and how to write practical tests for modern Laravel apps.
- What Is Unit Testing in Laravel?
- Pest vs PHPUnit in Laravel Testing
- Unit Tests vs Feature Tests in Laravel
- Prerequisites
- How Laravel Testing Is Structured
- How to Write Your First Laravel Unit Test
- Useful Assertions in Laravel Testing
- Mocking Dependencies in Laravel Unit Tests
- Checking Test Coverage
- Common Laravel Unit Testing Mistakes to Avoid
- Best Practices for Laravel Unit Testing
- Final Thoughts
What Is Unit Testing in Laravel?
Unit testing means you test a small piece of your code in isolation. In Laravel, it generally refers to testing a particular method, a certain service class, a single helper, or a specific business rule.
For instance, you can write a unit test to verify the following:
- A method of calculating taxes
- A discount rule
- A helper for formatting prices
- A service class that returns a certain value
- A method of determining user eligibility for an action
A unit test must be quick, focused, and simple to understand. It shouldn’t rely on a real database, an external API, a queue worker, or an email service.
Pest vs PHPUnit in Laravel Testing
Laravel lets you write tests using Pest or PHPUnit.
PHPUnit has been the testing framework for PHP for a long time. It’s often used to test small pieces of code in isolation (like methods, service classes, helpers, and business rules). Laravel also builds many of its testing helpers on top of PHPUnit, making it easier to test routes, controllers, validation, database changes, and other app behavior.
Pest is built on top of PHPUnit and has a cleaner, more expressive syntax. Most Laravel developers opt for Pest because it helps keep test files short and readable, especially for modern Laravel projects. Pest still has all the assertions that PHPUnit has and all the testing helpers that Laravel has, so you are not losing the core testing power behind PHPUnit.
Pest is a good starting point for most new Laravel projects. If you have existing projects that already use PHPUnit, then it’s totally fine to continue with PHPUnit. The testing concepts are the same.
Unit Tests vs Feature Tests in Laravel
In Laravel, there are two ways that you can test your application. You can do unit testing or feature testing. Before you start writing tests, you should first understand how they differ.
Laravel keeps the unit tests in the tests/Unit directory and feature tests in the tests/Feature directory. Unit tests verify small isolated bits of logic, and feature tests check how bigger parts of your app work together.

Laravel unit tests don’t normally boot up the whole Laravel application. This makes them fast but also means they should not rely on the database, container, facades, or framework services. Example of a unit test: A method of calculating order totals.
Feature tests make it possible to test a bigger flow. They can send HTTP requests, authenticate users, use factories, check database records, and test JSON API responses. Example of a feature test: A path to generate a purchase order.
So the simple rule is to use unit tests for your business logic and feature tests for Laravel behavior.
Prerequisites
Make sure you have a Laravel app ready for testing before you start. This tutorial requires the following:
- PHP 8.3 or higher
- Composer installed
- Laravel 12 or Laravel 13 application
- Pest or PHPUnit installed with your Laravel application
For hosting, we suggest using Cloudways to run your Laravel app. It gives you a managed cloud hosting environment with a Laravel-friendly stack so you can focus on writing and testing your app instead of managing server-level configuration.
How Laravel Testing Is Structured
Most Laravel applications have a test structure like this:

The default configuration for testing your Laravel app can be found in the phpunit.xml file. It tells the test runner how to run your tests and which environment settings to use when executing the tests.
For example, the phpunit.xml file defines separate test suites for unit and feature tests:
<testsuites>
<testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory suffix="Test.php">./tests/Feature</directory>
</testsuite>
</testsuites>
This tells Laravel where to find tests inside the tests/Unit and tests/Feature directories.
The base test class for Laravel testing is the file tests/TestCase.php. It helps keep the common test setup in one place, so individual test files can be cleaner.
Laravel includes this structure by default, so you can begin writing and running tests without having to build the configuration from the ground up.
How to Write Your First Laravel Unit Test
Let’s create a simple service class that calculates the final order total after the discount has been applied. This is great for a unit test, as the logic is small, isolated, and doesn’t depend on a route, database, controller, or external service.
Create a new directory named Services inside the app directory if it doesn’t already exist. Then, create this file: app/Services/OrderTotalCalculator.php
Add this code in that file:
<?php
namespace App\Services;
class OrderTotalCalculator
{
public function calculate(float $subtotal, float $discount): float
{
if ($discount < 0) {
$discount = 0;
}
if ($discount > $subtotal) {
$discount = $subtotal;
}
return round($subtotal - $discount, 2);
}
}
This class computes the final total while handling invalid discount values.
Creating a Test in Laravel
You can create a test using the make:test Artisan command. This command generates a new test class in the right test directory.
To create a unit test class:
php artisan make:test OrderTotalCalculatorTest --unit
Laravel will put this file in the tests/Unit directory.
To create a feature test class:
php artisan make:test UserTest
Laravel will put this file in the tests/Feature directory.
PHPUnit Example
Before writing the test, first decide what outcomes you want the method to handle. In this example, the calculate() method has three different cases: a normal discount, a discount larger than the subtotal, and a negative discount. These cases become your test methods.
Each test must assess one expected behavior. That way, when a test fails, you can quickly figure out what went wrong.
Open the file: tests/Unit/OrderTotalCalculatorTest.php
Add this test:
<?php
namespace Tests\Unit;
use App\Services\OrderTotalCalculator;
use PHPUnit\Framework\TestCase;
class OrderTotalCalculatorTest extends TestCase
{
public function test_it_calculates_order_total_after_discount(): void
{
$calculator = new OrderTotalCalculator();
$total = $calculator->calculate(100, 25);
$this->assertSame(75.0, $total);
}
public function test_discount_cannot_be_greater_than_subtotal(): void
{
$calculator = new OrderTotalCalculator();
$total = $calculator->calculate(100, 150);
$this->assertSame(0.0, $total);
}
public function test_negative_discount_is_treated_as_zero(): void
{
$calculator = new OrderTotalCalculator();
$total = $calculator->calculate(100, -20);
$this->assertSame(100.0, $total);
}
}
The assertSame() assertion checks both the value and the data type, which makes the test stricter.

The flow shows how a Laravel unit test works from start to finish. First, the test passes input values, such as a subtotal and a discount, to the method. The method then performs the calculation, and the assertion verifies that the returned result equals the expected value. If the returned value and type are equal, the test passes. Otherwise, the test fails and tells you what behavior to fix.
Pest Example
If you are using Pest in your Laravel project, you can write the same test with a shorter syntax.
<?php
use App\Services\OrderTotalCalculator;
it('calculates order total after discount', function () {
$calculator = new OrderTotalCalculator();
expect($calculator->calculate(100, 25))->toBe(75.0);
});
it('does not allow discount to be greater than subtotal', function () {
$calculator = new OrderTotalCalculator();
expect($calculator->calculate(100, 150))->toBe(0.0);
});
it('treats negative discount as zero', function () {
$calculator = new OrderTotalCalculator();
expect($calculator->calculate(100, -20))->toBe(100.0);
});
Pest’s expect()->toBe() is a strict assertion. It confirms that the value returned is as expected and of the expected type.
Pest is often selected for its cleaner syntax, but PHPUnit remains popular and fully supported. You can choose the one based on your team’s preference and the current setup of the project.
Running Tests in Laravel
You can run your entire test suite with:
php artisan test
You can also run tests directly with Pest or PHPUnit:
./vendor/bin/pest ./vendor/bin/phpunit
To run only unit tests:
php artisan test --testsuite=Unit
To run only feature tests:
php artisan test --testsuite=Feature
To run a specific test class:
php artisan test --filter=OrderTotalCalculatorTest
To stop after the first failure:
php artisan test --stop-on-failure
You can also use the Artisan test runner in Laravel to support arguments for PHPUnit and Pest. This lets you filter specific tests and check test coverage from the command line.
Useful Assertions in Laravel Testing
Assertions are used to verify that your code is doing what you expect it to do. In Laravel unit tests, you typically use PHPUnit assertions to compare return values, check boolean results, or verify values inside arrays and collections.
Here are some common PHPUnit assertions:

Boolean Assertions
Use assertTrue() and assertFalse() when a method returns a boolean value:
$this->assertTrue($user->isActive()); $this->assertFalse($user->isSuspended());
Value Comparison Assertions
Use assertSame() or assertEquals() to compare expected and actual values. Pass the expected value as the first argument and the actual result as the second argument:
$this->assertSame(100, $total);
$this->assertEquals('active', $status);
The difference is assertSame() checks for both value and data type, while assertEquals() checks only if the values are equal.
$this->assertEquals(100, '100'); // Passes $this->assertSame(100, '100'); // Fails
Here, 100 is an integer and ‘100’ is a string. They look similar but aren’t the same type. If the type is important, use assertSame(), which is generally safer for unit tests.
Null and Empty Assertions
Use assertNull() if a value should be exactly null. Use assertEmpty() if the value can be empty, such as an empty array or an empty string.
$this->assertNull($deletedAt); $this->assertEmpty($errors);
For instance,
- $deletedAt should be null if the record has not been deleted.
- $errors should be empty if there are no validation errors.
Array and Collection Assertions
Use assertCount() to check the number of items in an array or collection:
$this->assertCount(3, $items);
Use assertContains() to check if a value is present in an array or collection:
$this->assertContains('admin', $roles);
PHPUnit Assertions vs Laravel HTTP Assertions
Use PHPUnit assertions like assertSame(), assertTrue(), and assertCount() when writing unit tests for return values, arrays, collections, or business rules.
Laravel also provides HTTP assertions for feature tests. Use Laravel HTTP assertions like assertStatus(), assertSee(), and assertJson() when testing routes, pages, forms, or APIs.
Mocking Dependencies
Some portions of your application might depend on services you don’t want to run in a test. For example, payment gateways, API clients, notification services, and third-party tools can be slow, paid, unreliable, or out of your control.
In these cases, you may use a mock instead of the real dependency. A mock lets you define the response it should return. This allows you to test your app logic without calling out to the real service.
The following feature test extends Laravel’s Tests\TestCase and replaces the real PaymentGateway service with a mock using Laravel’s service container. This is required because $this->mock() and app() rely on the service container being booted.
use App\Services\PaymentGateway;
use Mockery\MockInterface;
use Tests\TestCase;
class PaymentGatewayTest extends TestCase {
public function test_payment_gateway_charge_is_successful(): void
{
$this->mock(PaymentGateway::class, function (MockInterface $mock) {
$mock->shouldReceive('charge')
->once()
->andReturn(true);
});
$paymentGateway = app(PaymentGateway::class);
$result = $paymentGateway->charge();
$this->assertTrue($result);
}
}
Here, the test instructs Laravel to replace PaymentGateway with a mock. The mock expects the charge() method to be called once and returns true, just like a successful payment would.
Mocking keeps the test focused on the behavior you want to verify and not on the real dependency behind it.
Checking Test Coverage
Test coverage tells you what percentage of your application code is executed when you run your tests. It helps you discover important pieces of the codebase that lack tests.
To check coverage, execute:
php artisan test --coverage
To generate a coverage report, your Laravel project must have a PHP coverage driver installed, such as Xdebug or PCOV.
You may also set a minimum coverage requirement using the command:
php artisan test --coverage --min=80
This command will fail if the coverage is less than 80 percent. It can be useful in CI/CD pipelines where you want to avoid new changes lowering test coverage too much. Don’t go after 100% coverage blindly.
It’s better to have robust tests for the critical business logic, payment flows, permissions, and important user actions than to have weak tests just to increase the number.
Common Laravel Unit Testing Mistakes to Avoid
Even simple tests are difficult to maintain if you don’t write them carefully. Here are some common mistakes to avoid when writing Laravel unit tests.
1. Test Everything as a Feature Test
Feature tests are useful, but they generally run slower than unit tests because they boot more of the Laravel app.
If you just want to test some small calculation, rule, helper, or service method, then write a unit test instead.
2. Putting Database Tests in Unit Tests
Laravel unit tests should be independent. If your test needs the database, a route, a factory, or the Laravel container, it’s probably a feature test.
Test small pieces of business logic with unit tests. Use feature tests when you want to test database changes or full app behavior.
3. Calling Real External APIs
Unit tests should not rely on real external services. Calling real APIs can make your tests slow, unreliable, or expensive.
Use mocks or fakes whenever your code depends on payment gateways, API clients, notification services, or other third-party tools.
4. Test Laravel Internals
You don’t need to test if Laravel can save a model, return a response, or resolve a service properly. Laravel already provides a test suite for the framework’s behavior.
Test your own logic and the rules specific to your application.
5. Writing Tests That Are Too Broad
A unit test should generally test one expected behavior. If a single test checks for five different outcomes, it is more complicated to understand why it failed.
Tests should be specific and have clear test names that describe the behavior being tested.
Best Practices for Laravel Unit Testing
Here are some practices to be followed while writing Laravel unit tests:
- Write small and specific unit tests.
- Test a single expected behavior for one test.
- Clearly name tests to describe the expected result.
- Move business logic to service classes to make testing easier.
- Mock dependencies that you don’t want to run during the test.
- Don’t use the database in unit tests.
- Don’t test Laravel internals.
- If both value and type are important, use strict assertions like assertSame().
- Add tests for bug fixes so the same issue doesn’t happen again.
- Test before every deployment.
- Add tests into your CI/CD pipeline.
Good unit tests should be easy to read, fast to run, and clear enough that another developer can understand what behavior is being tested.
Build and Test Laravel Apps on Managed Cloud Hosting
Writing reliable tests is easier when your Laravel app runs on a fast, stable, and developer-friendly platform. With Cloudways, you can deploy Laravel apps, manage servers easily, and focus more on building and testing your application.
Final Thoughts
Laravel unit testing allows you to catch issues early before they become bigger problems in production. This is particularly helpful in code that drives important business rules such as payment calculations, pricing logic, discounts, permissions, and order-related decisions.
You don’t have to test everything at once. Begin with those areas of your application where a small mistake can do real harm. Write tests when you add new functionality, fix bugs, or refactor existing code.
Good unit tests are small, clear, and easy to run. When they are well written, they make changes in the future safer, because you can quickly verify that your core logic still works as expected.
Well-tested Laravel apps are less prone to break during deployments, and as your codebase grows, those tests become increasingly valuable.
When you are ready to deploy your tested Laravel application, Cloudways provides a managed hosting platform with staging environments and deployment options, so you can focus on building and shipping reliable software instead of managing infrastructure.
Q1: What is the purpose of unit testing?
The purpose of unit testing is to determine whether a small piece of code is performing as expected. That generally means testing app logic like calculations, rules, helpers, or service methods in Laravel. Unit tests catch bugs early, make refactoring safer, and provide developers with more confidence when modifying existing code.
Q2: How do you run unit tests in Laravel?
You can run Laravel unit tests using the command: php artisan test –testsuite=Unit
Q3: What is PHPUnit in Laravel?
PHPUnit is a testing framework for PHP that Laravel uses for writing and executing tests. You can use PHPUnit to test sections of code like methods, service classes, helpers, and business rules in Laravel. Laravel’s testing features are mostly built on top of PHPUnit, which lets developers write unit tests as well as broader application tests.
Q4: Which is better, PHPUnit or Pest?
Both PHPUnit and Pest are good fits for Laravel testing. PHPUnit is a long-standing testing framework with a traditional class-based syntax. Pest is built on top of PHPUnit and provides a cleaner and more expressive syntax.
Use Pest if you want shorter, more readable tests. Use PHPUnit if your project already uses it or your team prefers the traditional testing style.
Q5: Is 100% unit test coverage possible?
Yes, you can have 100% unit test coverage. But it is not always necessary or useful. High coverage doesn’t always mean your tests are meaningful or your application is bug-free.
It’s better to test critical business logic, edge cases, and areas where bugs would cause real problems. A smaller set of meaningful tests is better than weak tests written solely to increase the coverage number.
Nisha Thomas
Nisha is a technical content writer with a passion for translating complex technology into content that’s clear, practical, and enjoyable to read. With strong technical insight and a user-first mindset, she crafts guides that help readers understand and use modern tools and platforms.