This commit is contained in:
2025-09-02 15:19:23 +02:00
commit 9b2b03b2ef
108 changed files with 166712 additions and 0 deletions

757
output/12.x/mocking.md Normal file
View File

@@ -0,0 +1,757 @@
# Mocking
* Introduction
* Mocking Objects
* Mocking Facades
* Facade Spies
* Interacting With Time
## 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](/docs/12.x/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:
Pest PHPUnit
1use App\Service;
2use Mockery;
3use Mockery\MockInterface;
4 
5test('something can be mocked', function () {
6 $this->instance(
7 Service::class,
8 Mockery::mock(Service::class, function (MockInterface $mock) {
9 $mock->expects('process');
10 })
11 );
12});
use App\Service;
use Mockery;
use Mockery\MockInterface;
test('something can be mocked', function () {
$this->instance(
Service::class,
Mockery::mock(Service::class, function (MockInterface $mock) {
$mock->expects('process');
})
);
});
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->expects('process');
11 })
12 );
13}
use App\Service;
use Mockery;
use Mockery\MockInterface;
public function test_something_can_be_mocked(): void
{
$this->instance(
Service::class,
Mockery::mock(Service::class, function (MockInterface $mock) {
$mock->expects('process');
})
);
}
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->expects('process');
6});
use App\Service;
use Mockery\MockInterface;
$mock = $this->mock(Service::class, function (MockInterface $mock) {
$mock->expects('process');
});
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->expects('process');
6});
use App\Service;
use Mockery\MockInterface;
$mock = $this->partialMock(Service::class, function (MockInterface $mock) {
$mock->expects('process');
});
Similarly, if you want to
[spy](http://docs.mockery.io/en/latest/reference/spies.html) 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');
use App\Service;
$spy = $this->spy(Service::class);
// ...
$spy->shouldHaveReceived('process');
## Mocking Facades
Unlike traditional static method calls, [facades](/docs/12.x/facades)
(including [real-time facades](/docs/12.x/facades#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}
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Cache;
class UserController extends Controller
{
/**
* Retrieve a list of all users of the application.
*/
public function index(): array
{
$value = Cache::get('key');
return [
// ...
];
}
}
We can mock the call to the `Cache` facade by using the `expects` method,
which will return an instance of a
[Mockery](https://github.com/padraic/mockery) mock. Since facades are actually
resolved and managed by the Laravel [service container](/docs/12.x/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:
Pest PHPUnit
1<?php
2 
3use Illuminate\Support\Facades\Cache;
4 
5test('get index', function () {
6 Cache::expects('get')
7 ->with('key')
8 ->andReturn('value');
9 
10 $response = $this->get('/users');
11 
12 // ...
13});
<?php
use Illuminate\Support\Facades\Cache;
test('get index', function () {
Cache::expects('get')
->with('key')
->andReturn('value');
$response = $this->get('/users');
// ...
});
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::expects('get')
13 ->with('key')
14 ->andReturn('value');
15 
16 $response = $this->get('/users');
17 
18 // ...
19 }
20}
<?php
namespace Tests\Feature;
use Illuminate\Support\Facades\Cache;
use Tests\TestCase;
class UserControllerTest extends TestCase
{
public function test_get_index(): void
{
Cache::expects('get')
->with('key')
->andReturn('value');
$response = $this->get('/users');
// ...
}
}
You should not mock the `Request` facade. Instead, pass the input you desire
into the [HTTP testing methods](/docs/12.x/http-tests) 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](http://docs.mockery.io/en/latest/reference/spies.html) 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:
Pest PHPUnit
1<?php
2 
3use Illuminate\Support\Facades\Cache;
4 
5test('values are stored in cache', function () {
6 Cache::spy();
7 
8 $response = $this->get('/');
9 
10 $response->assertStatus(200);
11 
12 Cache::shouldHaveReceived('put')->with('name', 'Taylor', 10);
13});
<?php
use Illuminate\Support\Facades\Cache;
test('values are stored in cache', function () {
Cache::spy();
$response = $this->get('/');
$response->assertStatus(200);
Cache::shouldHaveReceived('put')->with('name', 'Taylor', 10);
});
1use Illuminate\Support\Facades\Cache;
2 
3public function test_values_are_stored_in_cache(): void
4{
5 Cache::spy();
6 
7 $response = $this->get('/');
8 
9 $response->assertStatus(200);
10 
11 Cache::shouldHaveReceived('put')->with('name', 'Taylor', 10);
12}
use Illuminate\Support\Facades\Cache;
public function test_values_are_stored_in_cache(): void
{
Cache::spy();
$response = $this->get('/');
$response->assertStatus(200);
Cache::shouldHaveReceived('put')->with('name', 'Taylor', 10);
}
## 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:
Pest PHPUnit
1test('time can be manipulated', function () {
2 // Travel into the future...
3 $this->travel(5)->milliseconds();
4 $this->travel(5)->seconds();
5 $this->travel(5)->minutes();
6 $this->travel(5)->hours();
7 $this->travel(5)->days();
8 $this->travel(5)->weeks();
9 $this->travel(5)->years();
10 
11 // Travel into the past...
12 $this->travel(-5)->hours();
13 
14 // Travel to an explicit time...
15 $this->travelTo(now()->subHours(6));
16 
17 // Return back to the present time...
18 $this->travelBack();
19});
test('time can be manipulated', function () {
// Travel into the future...
$this->travel(5)->milliseconds();
$this->travel(5)->seconds();
$this->travel(5)->minutes();
$this->travel(5)->hours();
$this->travel(5)->days();
$this->travel(5)->weeks();
$this->travel(5)->years();
// Travel into the past...
$this->travel(-5)->hours();
// Travel to an explicit time...
$this->travelTo(now()->subHours(6));
// Return back to the present time...
$this->travelBack();
});
1public function test_time_can_be_manipulated(): void
2{
3 // Travel into the future...
4 $this->travel(5)->milliseconds();
5 $this->travel(5)->seconds();
6 $this->travel(5)->minutes();
7 $this->travel(5)->hours();
8 $this->travel(5)->days();
9 $this->travel(5)->weeks();
10 $this->travel(5)->years();
11 
12 // Travel into the past...
13 $this->travel(-5)->hours();
14 
15 // Travel to an explicit time...
16 $this->travelTo(now()->subHours(6));
17 
18 // Return back to the present time...
19 $this->travelBack();
20}
public function test_time_can_be_manipulated(): void
{
// Travel into the future...
$this->travel(5)->milliseconds();
$this->travel(5)->seconds();
$this->travel(5)->minutes();
$this->travel(5)->hours();
$this->travel(5)->days();
$this->travel(5)->weeks();
$this->travel(5)->years();
// Travel into the past...
$this->travel(-5)->hours();
// Travel to an explicit time...
$this->travelTo(now()->subHours(6));
// Return back to the present time...
$this->travelBack();
}
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});
$this->travel(5)->days(function () {
// Test something five days into the future...
});
$this->travelTo(now()->subDays(10), function () {
// Test something during a given moment...
});
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})
use Illuminate\Support\Carbon;
// Freeze time and resume normal time after executing closure...
$this->freezeTime(function (Carbon $time) {
// ...
});
// Freeze time at the current second and resume normal time after executing closure...
$this->freezeSecond(function (Carbon $time) {
// ...
})
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:
Pest PHPUnit
1use App\Models\Thread;
2 
3test('forum threads lock after one week of inactivity', function () {
4 $thread = Thread::factory()->create();
5 
6 $this->travel(1)->week();
7 
8 expect($thread->isLockedByInactivity())->toBeTrue();
9});
use App\Models\Thread;
test('forum threads lock after one week of inactivity', function () {
$thread = Thread::factory()->create();
$this->travel(1)->week();
expect($thread->isLockedByInactivity())->toBeTrue();
});
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}
use App\Models\Thread;
public function test_forum_threads_lock_after_one_week_of_inactivity()
{
$thread = Thread::factory()->create();
$this->travel(1)->week();
$this->assertTrue($thread->isLockedByInactivity());
}