Mocking
Laravel provides helpers for mocking events, jobs, and 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. You can also use or PHPUnit to create your own mocks or spies.
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 binding. This will instruct the container to use your mocked instance of the object instead of constructing the object itself:
In order to make this more convenient, you may use the mock
method, which is provided by Laravel’s base test case class:
use App\Service;
$this->mock(Service::class, function ($mock) {
$mock->shouldReceive('process')->once();
});
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:
use App\Service;
$this->partialMock(Service::class, function ($mock) {
$mock->shouldReceive('process')->once();
});
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:
use App\Service;
$this->spy(Service::class, function ($mock) {
$mock->shouldHaveReceived('process');
});
Bus Fake
As an alternative to mocking, you may use the Bus
facade’s fake
method to prevent jobs from being dispatched. When using fakes, assertions are made after the code under test is executed:
<?php
namespace Tests\Feature;
use App\Jobs\ShipOrder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Support\Facades\Bus;
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function testOrderShipping()
{
Bus::fake();
// Perform order shipping...
// Assert a specific type of job was dispatched meeting the given truth test...
Bus::assertDispatched(function (ShipOrder $job) use ($order) {
return $job->order->id === $order->id;
});
// Assert a job was not dispatched...
Bus::assertNotDispatched(AnotherJob::class);
}
}
Faking A Subset Of Events
If you only want to fake event listeners for a specific set of events, you may pass them to the fake
or fakeFor
method:
/**
* Test order process.
*/
public function testOrderProcess()
{
Event::fake([
OrderCreated::class,
]);
$order = Order::factory()->create();
Event::assertDispatched(OrderCreated::class);
// Other events are dispatched as normal...
$order->update([...]);
}
If you only want to fake event listeners for a portion of your test, you may use the fakeFor
method:
<?php
namespace Tests\Feature;
use App\Events\OrderCreated;
use App\Models\Order;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Event;
use Illuminate\Foundation\Testing\WithoutMiddleware;
class ExampleTest extends TestCase
{
/**
* Test order process.
*/
public function testOrderProcess()
{
$order = Event::fakeFor(function () {
$order = Order::factory()->create();
Event::assertDispatched(OrderCreated::class);
return $order;
// Events are dispatched as normal and observers will run ...
$order->update([...]);
}
}
HTTP Fake
The Http
facade’s fake
method allows you to instruct the HTTP client to return stubbed / dummy responses when requests are made. For more information on faking outgoing HTTP requests, please consult the HTTP Client testing documentation.
Mail Fake
You may use the Mail
facade’s fake
method to prevent mail from being sent. You may then assert that mailables were sent to users and even inspect the data they received. When using fakes, assertions are made after the code under test is executed:
<?php
namespace Tests\Feature;
use App\Mail\OrderShipped;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Support\Facades\Mail;
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function testOrderShipping()
{
Mail::fake();
// Assert that no mailables were sent...
Mail::assertNothingSent();
// Perform order shipping...
// Assert a specific type of mailable was dispatched meeting the given truth test...
Mail::assertSent(function (OrderShipped $mail) use ($order) {
return $mail->order->id === $order->id;
});
// Assert a message was sent to the given users...
Mail::assertSent(OrderShipped::class, function ($mail) use ($user) {
return $mail->hasTo($user->email) &&
$mail->hasCc('...') &&
$mail->hasBcc('...');
});
// Assert a mailable was sent twice...
Mail::assertSent(OrderShipped::class, 2);
// Assert a mailable was not sent...
Mail::assertNotSent(AnotherMailable::class);
}
}
If you are queueing mailables for delivery in the background, you should use the assertQueued
method instead of assertSent
:
Mail::assertQueued(...);
Mail::assertNotQueued(...);
Queue Fake
As an alternative to mocking, you may use the Queue
facade’s fake
method to prevent jobs from being queued. You may then assert that jobs were pushed to the queue and even inspect the data they received. When using fakes, assertions are made after the code under test is executed:
<?php
namespace Tests\Feature;
use App\Jobs\AnotherJob;
use App\Jobs\FinalJob;
use App\Jobs\ShipOrder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Support\Facades\Queue;
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function testOrderShipping()
{
Queue::fake();
// Assert that no jobs were pushed...
Queue::assertNothingPushed();
// Perform order shipping...
// Assert a specific type of job was pushed meeting the given truth test...
Queue::assertPushed(function (ShipOrder $job) use ($order) {
return $job->order->id === $order->id;
});
// Assert a job was pushed to a given queue...
Queue::assertPushedOn('queue-name', ShipOrder::class);
// Assert a job was pushed twice...
Queue::assertPushed(ShipOrder::class, 2);
// Assert a job was not pushed...
Queue::assertNotPushed(AnotherJob::class);
// Assert a job was pushed with a given chain of jobs, matching by class...
Queue::assertPushedWithChain(ShipOrder::class, [
FinalJob::class
]);
// Assert a job was pushed with a given chain of jobs, matching by both class and properties...
Queue::assertPushedWithChain(ShipOrder::class, [
new AnotherJob('foo'),
new FinalJob('bar'),
]);
// Assert a job was pushed without a chain of jobs...
Queue::assertPushedWithoutChain(ShipOrder::class);
}
}
Storage Fake
The Storage
facade’s fake
method allows you to easily generate a fake disk that, combined with the file generation utilities of the UploadedFile
class, greatly simplifies the testing of file uploads. For example:
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function testAlbumUpload()
{
Storage::fake('photos');
$response = $this->json('POST', '/photos', [
UploadedFile::fake()->image('photo1.jpg'),
UploadedFile::fake()->image('photo2.jpg')
]);
// Assert one or more files were stored...
Storage::disk('photos')->assertExists('photo1.jpg');
Storage::disk('photos')->assertExists(['photo1.jpg', 'photo2.jpg']);
// Assert one or more files were not stored...
Storage::disk('photos')->assertMissing('missing.jpg');
Storage::disk('photos')->assertMissing(['missing.jpg', 'non-existing.jpg']);
}
}
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:
public function testTimeCanBeManipulated()
{
// 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();
}
Facades
Unlike traditional static method calls, facades may be mocked. This provides a great advantage over traditional static methods and grants you the same testability you would have if you were using dependency injection. When testing, you may often want to mock a call to a Laravel facade in one of your controllers. For example, consider the following controller action:
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Cache;
class UserController extends Controller
{
/**
* Show a list of all users of the application.
*
* @return Response
*/
public function index()
{
$value = Cache::get('key');
//
}
}
We can mock the call to the Cache
facade by using the shouldReceive
method, which will return an instance of a 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: