4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

qnoteAdvent Calendar 2020

Day 5

Laravel でテストを書くことに慣れてきたあたりで知りたかったこと5選

Last updated at Posted at 2020-12-04

はじめに

年始に入社してから Laravel でテストを書くことが多かったように思います。
その中でハマったものや、既存のコードを読んでいてこういう書き方ができるのかと思ったことを5つ挙げさせていただきました。
最近テストを書き始めた方の参考になれば幸いです。

注入するクラスが例外を返すときの挙動をテストしたいとき

テスト対象へ注入しているクラスのインスタンスが下記の$hogeだとします。
以下のように例外発生時何か処理を行っているメソッドがテスト対象の場合、キャッチした中での処理結果を確認したいときがあると思います。
この場合、 $hoge->execute()の結果で、例外を返す必要があります。

try {
	$hoge->execute();
} catch (\Exception $e) {
	// エラー通知のメールを送信・ログに出力などの処理
}

このようなときは、以下のようにして例外を返すモックを作って注入してやれば例外を返してくれます。

$mock = $this->createMock(targetTestClass::class);
$mock->method('execute')  // メソッド名を指定
     ->will($this->throwException(new \Exception)); // 例外を返すことを指定

$test = new testTargetClass($mock);

注入するクラスがチェーンメソッドを使っている場合

テスト対象のクラスへ注入するクラス(下記の例ではHogeClass)から、チェーンメソッドが呼ばれている場合です。
チェーンメソッドが最終的に返す値によってテスト対象のメソッドの結果が変わる場合、任意の値を返してもらう必要があります。

class TestTargetClass
{
    protected $hoge;

    public function __construct(HogeClass $hoge)
    {
        $this->hoge = $hoge;
    }

		public function TestTargetMethod()
    {
			$tmp = $this->hoge->fuga()->moge(); // ←こういうパターンで任意の値を返したい!

			// この後 $tmp によって分岐したりしてメソッドが返す値が変わる

下記のようにチェーンメソッドで呼ばれているメソッド名を矢印でつないだ文字列を引き渡してあげればモックが作成できますのでこれを注入してやればOKです。

$hogeClassMock = \Mockery::mock(HogeClass::class);
$hogeClassMock
    ->shouldReceive('fuga->moge')
    ->andReturn('返したい値');

Log ファサードで出力した内容を確認したい場合(に便利なライブラリ)

Log fake for Laravel」というライブラリがシンプルで便利だったので紹介させてください。
Laravel のMailなどのファサードではデフォルトでFakeのようなメソッドがありテストがしやすいようになっていますが、Logにはないためそれを補ってくれます。

準備

下記のメソッドによって、実際のログファイルには出力されなくなるようになるため、テストのたびにログが積み重なっていくということも避けられます。(Mail::fake()に近い)

use Illuminate\Support\Facades\Log;
use TiMacDonald\Log\LogFake;

protected function setUp(): void
{
    parent::setUp();
    Log::swap(new LogFake());
}	

アサーションの書き方の例

以下のように、ログ出力先のチャンネル、エラーレベル、ログの内容をクロージャで指定するのが基本となっているようです。
他にもアサーションがありますので、上記のリンクから公式のドキュメントを参照してみてください。

Log::channel('hoge')->assertLogged(
    'error',
    function ($message) use ($expectedMessage) {
        return $expectedMessage === $message;
    }
);

2つの配列の順序は無視できるが、内容が一致しているかを確認したい場合

順序は関係ないが2つの配列の値が一致していたらテストとしてOKとしたいパターンです。
このメソッドを知るまでは都度ソートしてから比較しており、冗長のような気がする… などと思っていたらassertEqualsCanonicalizingというずばりなアサーションがありました。
以下のように配列を2つ引き渡してあげると、双方が持つ id の値が順序関係なく一致していればOKになります。

$this->assertEqualsCanonicalizing(
            $newData->pluck('id'),
            $oldData->pluck('id')
        );

特定の呼び出し回数で返す値を変えるモック

以下のようにandReturnUsingというメソッドに、呼び出し回数で処理を変えるクロージャを引き渡してやればOKです。
例では、sendメソッドが呼ばれた初回のみ例外を返し、2回目以降はfalseを返しています。
特定の値が引き渡された場合のみ異なる値を返すなど応用が効きそうです。

$mail = Mail::shouldReceive('send')
            ->andReturnUsing(function () {
                static $counter = 0;
                switch ($counter++) {
                    case 0:
                        throw new \Exception();
                        break;
                    default:
                        return false;
                        break;
                }
            });

単純に呼び出し回数で返す値を変えたい場合

andReturnに引き渡した値の順番と呼び出し回数は対応しているため、例えば、呼び出し1回目=>true、呼び出し2回目=>false と返したい場合は下記のように書けます。

Mail::shouldReceive('send')->andReturn(true, false);

おわりに

モックでも例外を返せたりチェーンメソッドが呼び出せるということは知っていればなんてことはありませんが、初めて見たときはどうしたらいいのかわからず困ったりしました。
せっかく方法を調べたので未来の自分向けのメモも兼ねて今回記事にまとめさせていただきました。
基本的なテストの書き方はわかった方が、どんどんテストを書いていくうちにそのうちつまずきそうなものを選んだつもりなので、最近テストを書き始めた方の頭の片隅にでも残してもらえれば幸いです。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?