Mocking

Introduction

When testing Laravel applications, you may wish to "mock" certain aspects of your application so they are not actually executed during a given test. For example, when testing a controller that dispatches an event, you may wish to mock the event listeners so they are not actually executed during the test. This allows you to only test the controller's HTTP response without worrying about the execution of the event listeners since the event listeners can be tested in their own test case.

Laravel provides helpful methods for mocking events, jobs, and other facades out of the box. These helpers primarily provide a convenience layer over Mockery so you do not have to manually make complicated Mockery method calls.

Mocking Objects

When mocking an object that is going to be injected into your application via Laravel's service container, you will need to bind your mocked instance into the container as an instance binding. This will instruct the container to use your mocked instance of the object instead of constructing the object itself:

1use App\Service;
2use Mockery;
3use Mockery\MockInterface;
4 
5public function test_something_can_be_mocked(): void
6{
7 $this->instance(
8 Service::class,
9 Mockery::mock(Service::class, function (MockInterface $mock) {
10 $mock->shouldReceive('process')->once();
11 })
12 );
13}
1use App\Service;
2use Mockery;
3use Mockery\MockInterface;
4 
5public function test_something_can_be_mocked(): void
6{
7 $this->instance(
8 Service::class,
9 Mockery::mock(Service::class, function (MockInterface $mock) {
10 $mock->shouldReceive('process')->once();
11 })
12 );
13}

In order to make this more convenient, you may use the mock method that is provided by Laravel's base test case class. For example, the following example is equivalent to the example above:

1use App\Service;
2use Mockery\MockInterface;
3 
4$mock = $this->mock(Service::class, function (MockInterface $mock) {
5 $mock->shouldReceive('process')->once();
6});
1use App\Service;
2use Mockery\MockInterface;
3 
4$mock = $this->mock(Service::class, function (MockInterface $mock) {
5 $mock->shouldReceive('process')->once();
6});

You may use the partialMock method when you only need to mock a few methods of an object. The methods that are not mocked will be executed normally when called:

1use App\Service;
2use Mockery\MockInterface;
3 
4$mock = $this->partialMock(Service::class, function (MockInterface $mock) {
5 $mock->shouldReceive('process')->once();
6});
1use App\Service;
2use Mockery\MockInterface;
3 
4$mock = $this->partialMock(Service::class, function (MockInterface $mock) {
5 $mock->shouldReceive('process')->once();
6});

Similarly, if you want to spy on an object, Laravel's base test case class offers a spy method as a convenient wrapper around the Mockery::spy method. Spies are similar to mocks; however, spies record any interaction between the spy and the code being tested, allowing you to make assertions after the code is executed:

1use App\Service;
2 
3$spy = $this->spy(Service::class);
4 
5// ...
6 
7$spy->shouldHaveReceived('process');
1use App\Service;
2 
3$spy = $this->spy(Service::class);
4 
5// ...
6 
7$spy->shouldHaveReceived('process');

Mocking Facades

Unlike traditional static method calls, facades (including real-time facades) may be mocked. This provides a great advantage over traditional static methods and grants you the same testability that you would have if you were using traditional dependency injection. When testing, you may often want to mock a call to a Laravel facade that occurs in one of your controllers. For example, consider the following controller action:

1<?php
2 
3namespace App\Http\Controllers;
4 
5use Illuminate\Support\Facades\Cache;
6 
7class UserController extends Controller
8{
9 /**
10 * Retrieve a list of all users of the application.
11 */
12 public function index(): array
13 {
14 $value = Cache::get('key');
15 
16 return [
17 // ...
18 ];
19 }
20}
1<?php
2 
3namespace App\Http\Controllers;
4 
5use Illuminate\Support\Facades\Cache;
6 
7class UserController extends Controller
8{
9 /**
10 * Retrieve a list of all users of the application.
11 */
12 public function index(): array
13 {
14 $value = Cache::get('key');
15 
16 return [
17 // ...
18 ];
19 }
20}

We can mock the call to the Cache facade by using the shouldReceive method, which will return an instance of a Mockery mock. Since facades are actually resolved and managed by the Laravel service container, they have much more testability than a typical static class. For example, let's mock our call to the Cache facade's get method:

1<?php
2 
3namespace Tests\Feature;
4 
5use Illuminate\Support\Facades\Cache;
6use Tests\TestCase;
7 
8class UserControllerTest extends TestCase
9{
10 public function test_get_index(): void
11 {
12 Cache::shouldReceive('get')
13 ->once()
14 ->with('key')
15 ->andReturn('value');
16 
17 $response = $this->get('/users');
18 
19 // ...
20 }
21}
1<?php
2 
3namespace Tests\Feature;
4 
5use Illuminate\Support\Facades\Cache;
6use Tests\TestCase;
7 
8class UserControllerTest extends TestCase
9{
10 public function test_get_index(): void
11 {
12 Cache::shouldReceive('get')
13 ->once()
14 ->with('key')
15 ->andReturn('value');
16 
17 $response = $this->get('/users');
18 
19 // ...
20 }
21}
lightbulb

You should not mock the Request facade. Instead, pass the input you desire into the HTTP testing methods such as get and post when running your test. Likewise, instead of mocking the Config facade, call the Config::set method in your tests.

Facade Spies

If you would like to spy on a facade, you may call the spy method on the corresponding facade. Spies are similar to mocks; however, spies record any interaction between the spy and the code being tested, allowing you to make assertions after the code is executed:

1use Illuminate\Support\Facades\Cache;
2 
3public function test_values_are_be_stored_in_cache(): void
4{
5 Cache::spy();
6 
7 $response = $this->get('/');
8 
9 $response->assertStatus(200);
10 
11 Cache::shouldHaveReceived('put')->once()->with('name', 'Taylor', 10);
12}
1use Illuminate\Support\Facades\Cache;
2 
3public function test_values_are_be_stored_in_cache(): void
4{
5 Cache::spy();
6 
7 $response = $this->get('/');
8 
9 $response->assertStatus(200);
10 
11 Cache::shouldHaveReceived('put')->once()->with('name', 'Taylor', 10);
12}

Interacting With Time

When testing, you may occasionally need to modify the time returned by helpers such as now or Illuminate\Support\Carbon::now(). Thankfully, Laravel's base feature test class includes helpers that allow you to manipulate the current time:

1use Illuminate\Support\Carbon;
2 
3public function test_time_can_be_manipulated(): void
4{
5 // Travel into the future...
6 $this->travel(5)->milliseconds();
7 $this->travel(5)->seconds();
8 $this->travel(5)->minutes();
9 $this->travel(5)->hours();
10 $this->travel(5)->days();
11 $this->travel(5)->weeks();
12 $this->travel(5)->years();
13 
14 // Travel into the past...
15 $this->travel(-5)->hours();
16 
17 // Travel to an explicit time...
18 $this->travelTo(now()->subHours(6));
19 
20 // Return back to the present time...
21 $this->travelBack();
22}
1use Illuminate\Support\Carbon;
2 
3public function test_time_can_be_manipulated(): void
4{
5 // Travel into the future...
6 $this->travel(5)->milliseconds();
7 $this->travel(5)->seconds();
8 $this->travel(5)->minutes();
9 $this->travel(5)->hours();
10 $this->travel(5)->days();
11 $this->travel(5)->weeks();
12 $this->travel(5)->years();
13 
14 // Travel into the past...
15 $this->travel(-5)->hours();
16 
17 // Travel to an explicit time...
18 $this->travelTo(now()->subHours(6));
19 
20 // Return back to the present time...
21 $this->travelBack();
22}

You may also provide a closure to the various time travel methods. The closure will be invoked with time frozen at the specified time. Once the closure has executed, time will resume as normal:

1$this->travel(5)->days(function () {
2 // Test something five days into the future...
3});
4 
5$this->travelTo(now()->subDays(10), function () {
6 // Test something during a given moment...
7});
1$this->travel(5)->days(function () {
2 // Test something five days into the future...
3});
4 
5$this->travelTo(now()->subDays(10), function () {
6 // Test something during a given moment...
7});

The freezeTime method may be used to freeze the current time. Similarly, the freezeSecond method will freeze the current time but at the start of the current second:

1use Illuminate\Support\Carbon;
2 
3// Freeze time and resume normal time after executing closure...
4$this->freezeTime(function (Carbon $time) {
5 // ...
6});
7 
8// Freeze time at the current second and resume normal time after executing closure...
9$this->freezeSecond(function (Carbon $time) {
10 // ...
11})
1use Illuminate\Support\Carbon;
2 
3// Freeze time and resume normal time after executing closure...
4$this->freezeTime(function (Carbon $time) {
5 // ...
6});
7 
8// Freeze time at the current second and resume normal time after executing closure...
9$this->freezeSecond(function (Carbon $time) {
10 // ...
11})

As you would expect, all of the methods discussed above are primarily useful for testing time sensitive application behavior, such as locking inactive posts on a discussion forum:

1use App\Models\Thread;
2 
3public function test_forum_threads_lock_after_one_week_of_inactivity()
4{
5 $thread = Thread::factory()->create();
6 
7 $this->travel(1)->week();
8 
9 $this->assertTrue($thread->isLockedByInactivity());
10}
1use App\Models\Thread;
2 
3public function test_forum_threads_lock_after_one_week_of_inactivity()
4{
5 $thread = Thread::factory()->create();
6 
7 $this->travel(1)->week();
8 
9 $this->assertTrue($thread->isLockedByInactivity());
10}

Comments

No Comments Yet

“Laravel” is a Trademark of Taylor Otwell.
The source documentation is released under MIT license. See laravel/docs on GitHub for details.
The translated documentations are released under MIT license. See cornch/laravel-docs-l10n on GitHub for details.