LaravelのMockいろいろ

目次

  1. 背景
  2. 1. mock関数を使う
    1. mock関数を使ったインスタンスの置き換え
    2. Mockery::mockを使う
  3. 2. $this→mock()を使う
  4. mock()を使うとき
  5. おまけ
  6. まとめ
  7. 関連記事

背景

こんにちは。 かりんとうマニア(@karintozuki)です。

皆さん、テスト書いてますか?テストを書く上でMockは欠かせないですよね。

LaravelではMockを作る方法がいくつかあります。私が働いているプロジェクトでは色々な書き方が混在していたので、それらを整理する意味でもここにMockの方法をまとめていきます。

この記事では、LaravelでMockを使用するさまざまな方法を紹介していきます。

コードサンプル内ではService という仮のクラスのrun()関数をモックする例を通じて紹介します。

1. mock関数を使う

mock関数を使ったインスタンスの置き換え

以下の例はmock関数を使った例です。

1
2
3
4
5
6
7
// mockを使ってMockを作る
$serviceMock = mock(Service::class);
$serviceMock->shouldReceive('run')->andReturn('Mocked Response')->once();

// MockをServiceクラスにバインドする
app()->instance(Service::class, $serviceMock);

ただ上記のコードはshouldReceiveandReturnの返り値はmock自身ではないことから

  • mockを生成する
  • mockを設定する
  • mockをバインドする

と記述が3行に渡っています。これはなんかすっきりしない、という人(私)もいると思います。

そこでgetMock()関数を使ってmock生成と設定を一気にやってしまうことができます。

1
2
3
4
5
6
7
8
9
10
// ワンライナーで書く
app()->instance(
Service::class,
mock(Service::class)
->shouldReceive('test')
->andReturn('mocked result')
->once()
->getMock()
)
);

もしくはmock()が第2引数に無名関数を取れることを利用してワンライナーっぽく書けます。

1
2
3
4
5
6
7
8
9
10
// ワンライナーで書く - その2
app()->instance(
Service::class,
mock(
Service::class,
fn (MockInterface $mock) => $mock->shouldReceive('test')
->andReturn('mocked result')
->once()
));

Mockery::mockを使う

Mockery::mock()はmock()と同じように使えます。

1
2
3
4
5
6
7
// Mockを作る
$mock = Mockery::mock(Service::class);
$mock->shouldReceive('run')->andReturn('Mocked Response')->once();

// MockをServiceクラスにバインドする
app()->instance(Service::class, $mock);

この方法は正直mock()があるのでわざわざ使わないかなと思います。

2. $this→mock()を使う

前節ではmockを作ってbindするという手順をふんでいましたが、$this→mock()を使うことでどちらもやってくれます。

以下の例は$this→mock()を使った例です。

1
2
3
4
5
6
7
8

$this->mock(Service::class,
function (MockInterface $mock): void {
$mock->shouldReceive('run')
->andReturn('Mocked Response')
->once();
});

partialMock関数を使うとpartial mockが作れます。

1
2
3
4
5
6
7
$this->partialMock(Service::class, 
function (MockInterface $mock): void {
$mock->shouldReceive('run')
->andReturn('Mocked Response')
->once();
});

mock()を使うとき

そんな感じで優秀な$this→mockなのですが、万能ではありません。

app()→instance()のかわりにapp()→bind()を使いたいときにはmock()を使う必要があります。

例えば、モックするクラスのインスタンスを作る際に引数を渡したいときなどが該当します。

1
2
3
4
5
6
7
8
9
10
11
app()->bind(
Service::class,
function(Application $app, array $params) {
$mock = mock(Service::class);
$mock->shouldReceive('run')->andReturn('Mocked Response')->once();
return $mock;
});

// 引数を渡すときはapp()->instance()が使えない
$serviceMock = app()->make(Service::class, ['param1' => 'test parameter']);

私のプロジェクトではapp()->bind()も使いたかったので、$this->mockmock()が混在しないようにmock()で統一しようということになりました。

ケースバイケースだったり個人の好みによるところが多いのでチームメンバーと話して決めるのがいいでしょう。

おまけ

上記の例ではいつもonce()を呼んでいます。
これをしないとモックした関数がテスト中に呼ばれなくてもエラーを吐かないので予期せずテストが通ってしまうことがあります。

shouldReceiveが効いてない!とパニくった実体験が私はあるので、皆さんは気をつけましょう。

まとめ

Laravelには(良くも悪くも)Mockひとつとっても様々な書き方があります。
$this→mockを使うかmockを使うかはケースバイケースなので、
チームで統一性がとれるルールを選んで使いましょう。

それじゃ今日はこの辺で。

関連記事

こちらの記事もおすすめです。

DockerでLaravel(PHP8)&Apache&MySQLを動かす