Back to all articles
Laravel Insights Jan 7, 2026 โˆ™ 1 min read

Mastering Laravel Testing with Pest: A Guide

Explore real-world examples of testing services, controllers, collections, and more using Pest in Laravel

An illustration showing the Laravel and Pest PHP logos side-by-side, symbolizing a powerful partnership for elegant and efficient application testing.

Mastering Laravel Testing with Pest

Explore real-world examples of testing services, controllers, collections, and more using Pest in Laravel.

Automated testing is a non-negotiable discipline for building reliable, maintainable, and scalable Laravel applications. A robust test suite acts as a safety net, allowing development teams to ship complex features with confidence and refactor code without introducing regressions. While Laravel has always offered first-class support for testing with PHPUnit, the introduction of Pest has brought a new level of simplicity and elegance to the process.

Pest is a testing framework built on top of PHPUnit that focuses on developer experience. Its expressive, minimal syntax reduces boilerplate and makes tests more readable, encouraging developers to write more tests. For teams looking to accelerate project delivery and enhance code quality, adopting Pest is a direct path to a more efficient and enjoyable testing workflow.

This guide provides a comprehensive walkthrough for mastering testing in Laravel with Pest. We will cover everything from initial setup to writing unit and feature tests for various application components. You will find real-world examples for testing services, controllers, and collections, along with advanced techniques like mocking and working with model factories.

Setting Up Pest in Your Laravel Project

Integrating Pest into a new or existing Laravel project is a streamlined process. The setup involves installing the necessary Composer packages and running an artisan command to configure your testing environment.

First, require Pest and its Laravel plugin as development dependencies:

composer require pestphp/pest --dev --with-all-dependencies
composer require pestphp/pest-plugin-laravel --dev

Next, run the pest:install command. This will publish the necessary configuration files, including a tests/Pest.php file for global test helpers and a tests/Feature/ExampleTest.php to get you started.

php artisan pest:install

To confirm that everything is set up correctly, run the Pest test runner from your project's root directory:

./vendor/bin/pest

You should see output indicating that your example tests have passed. With the setup complete, you are ready to write your first test.

Real-World Testing Examples with Pest

Pest's elegant syntax shines when applied to real-world scenarios. Let's explore how to test various components of a typical Laravel application.

Testing a Service Class

Unit tests are designed to test small, isolated pieces of code, such as a method within a service class. Imagine a UsernameService responsible for generating a unique username from a person's name.

// app/Services/UsernameService.php
namespace App\Services;

class UsernameService
{
    public function generate(string $name): string
    {
        return strtolower(str_replace(' ', '_', $name));
    }
}

A Pest unit test for this service would verify that the generate method produces the correct output for different inputs.

// tests/Unit/UsernameServiceTest.php
use App\Services\UsernameService;

test('it generates a correct username from a given name', function () {
    $service = new UsernameService();

    $username = $service->generate('John Doe');

    expect($username)->toBe('john_doe');
});

The test() function defines a test case, and expect() provides access to Pest's powerful and readable expectation API.

Testing a Controller

Feature tests validate larger pieces of functionality, often by simulating an HTTP request to a controller. Let's test a RegisterUserController that handles user registration. The test should assert that a user can register and that the new user is stored in the database.

// tests/Feature/RegistrationTest.php
use App\Models\User;
use function Pest\Laravel\{post};

test('a user can register for an account', function () {
    $userData = [
        'name' => 'Jane Doe',
        'email' => 'jane@example.com',
        'password' => 'password',
        'password_confirmation' => 'password',
    ];

    // Simulate a POST request to the registration endpoint
    post(route('register'), $userData)
        ->assertRedirect(route('home'));

    // Assert that the user was created in the database
    $this->assertDatabaseHas('users', [
        'email' => 'jane@example.com',
    ]);
});

This test covers several critical aspects:

  • It simulates a real user interaction by sending a POST request.
  • It asserts the correct HTTP response, in this case, a redirect.
  • It uses a database assertion (assertDatabaseHas) to confirm that the application's state changed as expected.

Testing Custom Collection Methods

Laravel's collections are incredibly powerful, and you may find yourself adding custom macros to extend their functionality. For example, you might create a toCsv macro. Testing this ensures it works as expected.

// In a service provider's boot() method
use Illuminate\Support\Collection;

Collection::macro('toCsv', function () {
    return $this->map(function ($value) {
        return implode(',', $value);
    })->implode("\n");
});

// tests/Unit/CollectionMacroTest.php
test('collection toCsv macro converts data correctly', function () {
    $collection = collect([
        ['id' => 1, 'name' => 'Product A', 'price' => 100],
        ['id' => 2, 'name' => 'Product B', 'price' => 200],
    ]);

    $csv = $collection->toCsv();

    $expectedCsv = "1,Product A,100\n2,Product B,200";

    expect($csv)->toEqual($expectedCsv);
});

Advanced Testing Techniques

As your application grows, you will need more advanced testing techniques to handle complex scenarios and external dependencies.

Database Assertions and Factories

Laravel's model factories are essential for creating test data efficiently. Instead of manually creating models, you can use factories to generate predictable test data.

This example tests an API endpoint that returns a list of published posts.

// tests/Feature/ListPostsTest.php
use App\Models\Post;
use function Pest\Laravel\getJson;

test('api returns a list of published posts', function () {
    // Arrange: Create 3 published posts and 2 draft posts
    Post::factory()->count(3)->create(['published_at' => now()]);
    Post::factory()->count(2)->create(['published_at' => null]);

    // Act: Make a request to the posts endpoint
    $response = getJson('/api/posts');

    // Assert: Check the response
    $response->assertOk();
    $response->assertJsonCount(3, 'data');
});

Factories make it simple to set up a specific database state for your test. The assertions then confirm the endpoint correctly filters the data.

Mocking and Spying

Your application will inevitably interact with external services, such as a payment gateway or notification service. In your tests, you should mock these services to isolate your application code and avoid making real network requests.

Imagine a service that sends a notification when an order is completed.

// tests/Feature/OrderCompletionTest.php
use App\Services\NotificationService;
use Mockery\MockInterface;

test('a notification is sent when an order is completed', function () {
    // Mock the NotificationService
    $this->mock(NotificationService::class, function (MockInterface $mock) {
        // Expect the 'send' method to be called once with specific arguments
        $mock->shouldReceive('send')
             ->once()
             ->with('customer@example.com', 'Your order has been shipped!');
    });

    // Act: Trigger the action that completes an order
    // This could be a controller action or another service call
    $orderService = app(OrderService::class);
    $orderService->completeOrder($order);
});

The mock() helper replaces the NotificationService in Laravel's service container with a test double. The test passes only if the send method is called exactly as expected.

Best Practices for Efficient Testing

  1. Structure Your Tests Clearly: Use Pest's describe() blocks to group related tests. Follow the "Arrange, Act, Assert" pattern to make your tests easy to understand.
  2. Use the RefreshDatabase Trait: For feature tests that interact with the database, use the Uses helper in Pest.php to automatically apply the RefreshDatabase trait. This ensures each test runs against a clean, migrated database.
  3. Keep Tests Focused: Each test should verify a single behavior. This makes it easier to identify the exact cause of a failure.
  4. Filter and Debug: When debugging a failing test, use the --filter option to run only that specific test. You can also use Pest's ->only() method on a test to isolate it.

Conclusion

Mastering testing is a crucial step in the journey of a professional Laravel developer. Pest simplifies this journey by offering a clean, expressive, and powerful framework that makes writing tests a more intuitive and enjoyable process. By integrating the real-world examples and advanced techniques covered in this guide, you can build a comprehensive test suite that enhances your application's reliability and accelerates your development workflow. Adopting Pest is an investment in code quality, maintainability, and your team's long-term productivity.


Related articles

Continue exploring Laravel insights and practical delivery strategies.

Laravel consulting

Need senior Laravel help for this topic?

Let's adapt these practices to your product and deliver the next milestone.