Stop hitting live APIs in your tests. Learn how to use test doubles, stubs, and mocks to isolate your Laravel application logic for faster, reliable tests.
Previously in this course, we explored Advanced Testing: Integration Tests in Laravel, where we covered maintaining database state across complex workflows. While integration tests are essential, they can become slow and fragile when they rely on third-party APIs like Stripe, Twilio, or GitHub.
In this lesson, we move beyond database-centric testing to isolate your business logic entirely using test doubles. By replacing real network-dependent objects with controlled substitutes, you ensure your tests remain deterministic, lightning-fast, and decoupled from the instability of the outside world.
In testing, a "test double" is a generic term for any object used in place of a real production object. Think of it like a stunt double in a movie: they look and act like the real thing, but they are under your complete control.
We primarily use three types of doubles in Laravel:
Mail::fake()).Imagine our project board application needs to fetch a user's GitHub profile to display their avatar. If we hit the GitHub API every time we run our test suite, we risk hitting rate limits or causing intermittent failures due to network latency.
Instead, we mock the service. First, ensure your service is behind an interface, which we touched on in our Repository Pattern Fundamentals lesson.
Suppose we have a GitHubClientInterface. We want to test that our ProfileService correctly handles the user's data.
PHP#6A9955">// In your test file public function test_it_returns_formatted_profile_data() { #6A9955">// 1. Create a mock of the interface $mock = Mockery::mock(GitHubClientInterface::class); #6A9955">// 2. Define the expectation(The Stub) $mock->shouldReceive('getUser') ->once() ->with('laravel-user') ->andReturn(['name' => 'John Doe', 'avatar' => 'https:#6A9955">//path.to/image.jpg']); #6A9955">// 3. Bind the mock into the Laravel Container $this->app->instance(GitHubClientInterface::class, $mock); #6A9955">// 4. Run the code that uses the service $service = app(ProfileService::class); $result = $service->getProfile('laravel-user'); #6A9955">// 5. Assert the result $this->assertEquals('John Doe', $result['name']); }
By binding the mock into the container, any class that resolves GitHubClientInterface via dependency injection will receive our "stunt double" instead of the real HTTP client.
For our running project, implement a test for your TaskAssignmentService.
php artisan make:test TaskAssignmentTest.Mockery::mock() to simulate a successful response from that service.Task record in the database.Even senior engineers fall into these traps when using test doubles:
setUp, ensure you are aware of the scope. Laravel's Mockery integration usually cleans up, but manual instance() bindings might persist if not handled carefully.Test doubles are the secret to a stable CI/CD pipeline. By using stubs to provide data and mocks to verify interactions, you decouple your business logic from the unpredictable nature of external infrastructure. This is the foundation for the Mocking Services and Repositories in Laravel Tests pattern, allowing you to build complex features with absolute confidence.
Up next: We'll dive into Testing Events and Jobs, where we'll leverage Laravel's built-in fakes to verify asynchronous workflows without actually firing off background processes.
Master dependency injection and mocking to isolate your controllers. Learn to simulate failures and test your business logic without hitting the database.
Read moreMaster Testing DDD components in Laravel. Learn to mock external services, isolate domain logic, and write reliable PHPUnit tests for your Action classes.
Testing with Test Doubles