本文へ移動
Laravel Tips

モック(にせものに置きかえる)

テストで本物の代わりににせもの(モック・スパイ)を置く方法と、ファサードのにせもの、時間を進めたり止めたりする方法を説明します。

モックとは、テストのあいだだけ本物の代わりに置く、にせものの部品です。たとえば、コントローラーがイベント(「〜が起きた」という知らせ)を出すとき、その知らせを受けて動く処理(リスナー)まで動かしたくないことがあります。そういうときにリスナーをにせものにすれば、コントローラーが返す HTTP のレスポンスだけを確かめられます。リスナーは、それ専用のテストで別に確かめます。

Laravel は、イベントやジョブなどをにせものにする命令を最初から用意しています。これらは、Mockery というモックの道具を、むずかしい書き方をしなくても使えるようにしたものです。テストの基本はテストのはじめかたを見てください。

オブジェクトをにせものにする#

サービスコンテナ(クラスを作って渡してくれる、道具箱のようなしくみ)を通してアプリに渡されるオブジェクトをにせものにするときは、にせものを instance メソッドでコンテナに登録します。そうすると、コンテナは本物を作る代わりに、にせものを使います。

php
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');
        })
    );
});

PHPUnit で書くときは、メソッドの中に同じ内容を書きます。

php
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');
        })
    );
}

もっと簡単に書くために、テストの土台のクラスには mock メソッドがあります。次の例は、上の例と同じ意味です。

php
use App\Service;
use Mockery\MockInterface;

$mock = $this->mock(Service::class, function (MockInterface $mock) {
    $mock->expects('process');
});

オブジェクトのうち、いくつかのメソッドだけをにせものにしたいときは partialMock を使います。にせものにしなかったメソッドは、ふつうに動きます。

php
use App\Service;
use Mockery\MockInterface;

$mock = $this->partialMock(Service::class, function (MockInterface $mock) {
    $mock->expects('process');
});

スパイ(Mockery の spy)を使いたいときは、spy メソッドがあります。スパイはモックに似ていますが、テストされるコードとのやりとりをすべて記録しておきます。そのため、コードを動かしたあとに「こう呼ばれたはず」と確かめられます。

php
use App\Service;

$spy = $this->spy(Service::class);

// ...

$spy->shouldHaveReceived('process');
命令 説明
instance 作ったにせものを、コンテナに登録する
mock にせものを作って、コンテナに登録する
partialMock 一部のメソッドだけをにせものにする。ほかはふつうに動く
spy やりとりを記録するにせものを作る。あとで確かめる

ファサードをにせものにする#

ファサード(Cache::get() のように、クラス名と :: で機能を呼べる窓口)は、にせものにできます。ふつうの静的メソッド(:: で呼ぶメソッド)にはできないことです。どのクラスもファサードのように使える「リアルタイムファサード」も同じです。これは大きな利点です。依存性の注入(必要な道具を、自分で作らずに外から渡してもらうこと)を使ったときと同じくらい、テストしやすくなります。

次のコントローラーを考えます。

php
<?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 [
            // ...
        ];
    }
}

Cache ファサードの呼び出しは、expects メソッドでにせものにできます。expects は、Mockery のモックを返します。ファサードは、実はサービスコンテナが作って管理しているので、ふつうの静的クラスよりもずっとテストしやすいのです。次の例は、Cache の get をにせものにします。

php
<?php

use Illuminate\Support\Facades\Cache;

test('get index', function () {
    Cache::expects('get')
        ->with('key')
        ->andReturn('value');

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

    // ...
});

PHPUnit で書くとき:

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

        // ...
    }
}

注意

Request ファサードはにせものにしないでください。代わりに、get や post などの HTTP テストの命令に、ほしい入力を渡します。同じように、Config ファサードをにせものにする代わりに、テストの中で Config::set を呼びます。

ファサードのスパイ#

ファサードにもスパイを使えます。そのファサードの spy メソッドを呼びます。スパイは、やりとりを記録しておき、あとで確かめられます。

php
<?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);
});

PHPUnit で書くとき:

php
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);
}

時間を動かす#

now ヘルパー関数や Illuminate\Support\Carbon::now() が返す時刻を、テストのあいだだけ変えたいことがあります。テストの土台のクラスに、時刻を動かす命令があります。

php
test('time can be manipulated', function () {
    // 未来へ進む
    $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();

    // 過去へ戻る
    $this->travel(-5)->hours();

    // 決まった時刻へ移る
    $this->travelTo(now()->minus(hours: 6));

    // いまの時刻へ戻る
    $this->travelBack();
});

PHPUnit で書くとき:

php
public function test_time_can_be_manipulated(): void
{
    // 未来へ進む
    $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();

    // 過去へ戻る
    $this->travel(-5)->hours();

    // 決まった時刻へ移る
    $this->travelTo(now()->minus(hours: 6));

    // いまの時刻へ戻る
    $this->travelBack();
}

時刻を動かす命令には、クロージャ(名前のない関数)も渡せます。クロージャは、指定した時刻で時間が止まった状態で動きます。終わると、時間はふつうに流れ出します。

php
$this->travel(5)->days(function () {
    // 5日後の世界でテストする
});

$this->travelTo(now()->minus(days: 10), function () {
    // 決まった時刻のようすをテストする
});

freezeTime は、いまの時刻を止めます。freezeSecond は、いまの時刻を「いまの秒の始まり」で止めます。

php
use Illuminate\Support\Carbon;

// 時間を止めて、クロージャが終わったら元に戻る
$this->freezeTime(function (Carbon $time) {
    // ...
});

// いまの秒の始まりで時間を止めて、クロージャが終わったら元に戻る
$this->freezeSecond(function (Carbon $time) {
    // ...
});
命令 説明
travel(数)->milliseconds() ミリ秒だけ時間を進める(マイナスなら戻す)
travel(数)->seconds() 秒だけ時間を進める
travel(数)->minutes() 分だけ時間を進める
travel(数)->hours() 時間だけ進める
travel(数)->days() 日だけ進める
travel(数)->weeks() 週だけ進める
travel(数)->years() 年だけ進める
travelTo 決めた時刻へ移る
travelBack いまの時刻へ戻る
freezeTime いまの時刻で時間を止める
freezeSecond いまの秒の始まりで時間を止める

時間に関わる動きを確かめるときに、これらが役に立ちます。たとえば、掲示板で、1週間動きの無かった投稿に鍵をかける機能です。

php
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();
});

PHPUnit で書くとき:

php
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());
}

関連するページ#

公式ドキュメント(英語)

2026年10月5日時点の内容をもとに、日本語でまとめています。

ページの一覧