w3resource

Laravel (5.7) Mocking

Introduction

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

Laravel provides you with helpers for mocking events, jobs, and facades out of the box. These helpers primarily provide a convenience layer over Mockery so that you do not have to manually make complicated Mockery method calls. You can also use PHPUnit or Mockery to create your own mocks or spies.

Bus Fake

Alternatively, rather than using mocking, you can use the Bus facade's fake method to prevent jobs from being dispatched. When you are using fakes, assertions will be made after the code under test is executed:

<?php

namespace Tests\Feature;

use Tests\TestCase;
use App\Jobs\ShipOrder;
use Illuminate\Support\Facades\Bus;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;

class ExampleTest extends TestCase
{
    public function testOrderShipping()
    {
        Bus::fake();

        // Perform order shipping...

        Bus::assertDispatched(ShipOrder::class, function ($job) use ($order) {
            return $job->order->id === $order->id;
        });

        // Asserts a job was not dispatched...
        Bus::assertNotDispatched(AnotherJob::class);
    }
}

Event Fake

Rather than using mocking, you can use the Event facade's fake method to prevent all event listeners from executing. You can then assert that events were dispatched and even inspect the data they received. When you use fakes, assertions will be made after the code under test is executed:

<?php

namespace Tests\Feature;

use Tests\TestCase;
use App\Events\OrderShipped;
use App\Events\OrderFailedToShip;
use Illuminate\Support\Facades\Event;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;

class ExampleTest extends TestCase
{
    /**
     * Test order shipping.
     */
    public function testOrderShipping()
    {
        Event::fake();

        // Perform order shipping...

        Event::assertDispatched(OrderShipped::class, function ($e) use ($order) {
            return $e->order->id === $order->id;
        });

        // Asserts an event was dispatched twice...
        Event::assertDispatched(OrderShipped::class, 2);

        // Asserts an event was not dispatched...
        Event::assertNotDispatched(OrderFailedToShip::class);
    }
}

Faking A Subset Of Events

In the case where you only want to fake event listeners for a specific set of events, you can pass them to the fake or fakeFor method:

/**
 * Test order process.
 */
public function testOrderProcess()
{
    Event::fake([
        OrderCreated::class,
    ]);

    $order = factory(Order::class)->create();

    Event::assertDispatched(OrderCreated::class);

    // Other events are dispatched as normal...
    $order->update([...]);
}

Scoped Event Fakes

In cases where you only want to fake event listeners for a portion of your test, you can use the fakeFor method:

<?php

namespace Tests\Feature;

use App\Order;
use Tests\TestCase;
use App\Events\OrderCreated;
use Illuminate\Support\Facades\Event;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;

class ExampleTest extends TestCase
{
    /**
     * Test order process.
     */
    public function testOrderProcess()
    {
        $order = Event::fakeFor(function () {
            $order = factory(Order::class)->create();

            Event::assertDispatched(OrderCreated::class);

            return $order;
        });

        // Events are dispatched as normal and observers will run ...
        $order->update([...]);
    }
}

Mail Fake

You can use the Mail facade's fake method to prevent mail from being sent. You can then assert that mailables were sent to users and even inspect the data they received. When you use fakes, assertions will be made after the code under test is executed:

<?php

namespace Tests\Feature;

use Tests\TestCase;
use App\Mail\OrderShipped;
use Illuminate\Support\Facades\Mail;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;

class ExampleTest extends TestCase
{
    public function testOrderShipping()
    {
        Mail::fake();

        // Asserts that no mailables were sent...
        Mail::assertNothingSent();

        // Perform order shipping...

        Mail::assertSent(OrderShipped::class, function ($mail) use ($order) {
            return $mail->order->id === $order->id;
        });

        // Asserts 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('...');
        });

        // Asserts a mailable was sent twice...
        Mail::assertSent(OrderShipped::class, 2);

        // Assert a mailable was not sent...
        Mail::assertNotSent(AnotherMailable::class);
    }
}

In cases where you are queueing mailables for delivery in the background, you will need to use the assertQueued method instead of assertSent:

Mail::assertQueued(...);
Mail::assertNotQueued(...);

Notification Fake

You can use the Notification facade's fake method to prevent notifications from being sent. You can then assert that notifications were sent to users and even inspect the data that they received. When you are using fakes, assertions will be made after the code under test is executed:

<?php

namespace Tests\Feature;

use Tests\TestCase;
use App\Notifications\OrderShipped;
use Illuminate\Support\Facades\Notification;
use Illuminate\Notifications\AnonymousNotifiable;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;

class ExampleTest extends TestCase
{
    public function testOrderShipping()
    {
        Notification::fake();

        // Asserts that no notifications were sent...
        Notification::assertNothingSent();

        // Perform order shipping...

        Notification::assertSentTo(
            $user,
            OrderShipped::class,
            function ($notification, $channels) use ($order) {
                return $notification->order->id === $order->id;
            }
        );

        // Asserts a notification was sent to the given users...
        Notification::assertSentTo(
            [$user], OrderShipped::class
        );

        // Asserts a notification was not sent...
        Notification::assertNotSentTo(
            [$user], AnotherNotification::class
        );

        // Asserts a notification was sent via Notification::route() method...
        Notification::assertSentTo(
            new AnonymousNotifiable, OrderShipped::class
        );            
    }
}

Queue Fake

Rather than mocking, you can use the Queue facade's fake method to prevent jobs from being queued. Then you can assert that jobs were pushed to the queue and even inspect the data that they received. When you use fakes, assertions will be made after the code under test is executed:

<?php

namespace Tests\Feature;

use Tests\TestCase;
use App\Jobs\ShipOrder;
use Illuminate\Support\Facades\Queue;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;

class ExampleTest extends TestCase
{
    public function testOrderShipping()
    {
        Queue::fake();

        // Asserts that no jobs were pushed...
        Queue::assertNothingPushed();

        // Perform order shipping...

        Queue::assertPushed(ShipOrder::class, function ($job) use ($order) {
            return $job->order->id === $order->id;
        });

        // Asserts that a job was pushed to a given queue...
        Queue::assertPushedOn('queue-name', ShipOrder::class);

        // Asserts a job was pushed twice...
        Queue::assertPushed(ShipOrder::class, 2);

        // Asserts a job was not pushed...
        Queue::assertNotPushed(AnotherJob::class);

        // Asserts a job was pushed with a specific chain...
        Queue::assertPushedWithChain(ShipOrder::class, [
            AnotherJob::class,
            FinalJob::class
        ]);
    }
}

Storage Fake

The Storage facade's fake method will allow you to easily generate a fake disk that, when combined with the file generation utilities of the UploadedFile class, will greatly simplifies the testing of file uploads. For instance:

<?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;

class ExampleTest extends TestCase
{
    public function testAvatarUpload()
    {
        Storage::fake('avatars');

        $response = $this->json('POST', '/avatar', [
            'avatar' => UploadedFile::fake()->image('avatar.jpg')
        ]);

        // Asserts the file was stored...
        Storage::disk('avatars')->assertExists('avatar.jpg');

        // Asserts a file does not exist...
        Storage::disk('avatars')->assertMissing('missing.jpg');
    }
}

Facades

Facades can be mocked, unlike the traditional static method calls. This will provide a great advantage over traditional static methods and grants you the same testability you would have if you were to use dependency injection. When testing, you often may want to mock a call to a Laravel facade in one of your controllers. For instance, consider the following controller action below:

<?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');

        //
    }
}

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

<?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Support\Facades\Cache;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;

class UserControllerTest extends TestCase
{
    public function testGetIndex()
    {
        Cache::shouldReceive('get')
                    ->once()
                    ->with('key')
                    ->andReturn('value');

        $response = $this->get('/users');

        // ...
    }
}

Previous: Laravel (5.7) Browser Testing (Laravel Dusk)
Next: Laravel (5.7) Laravel Scout



Follow us on Facebook and Twitter for latest update.