Laravel, PHPUnit, Mockery環境のテストでサッと使いたい手法5つ


これは何?

普段Laravelでテストを書いているのですが、「この部分どうやってテスト書いたらいいだろうか…?」ということがたまにあります。そんなときに個人的にサッと使えるようにしておきたいと思ったメソッド・関数などを記載しています😀

タイトルにもありますが、前提として想定しているテストフレームワークとモックオブジェクトフレームは下記です。

では始めていきます。

$this->mock()を使う

Laravelで開発していると、DIが便利で使うのが普通になって来るのではと思います。ServiceProviderを使用して、クラスのコンストラクタでプロパティにオブジェクトをセットして…という方法もありますが、コンストラクタでインジェクションせずとも app() ヘルパーを使うことでオブジェクトを呼び出すことも可能です。

class Foo
{
    public function isBar()
    {
        // trueが返る
        return app(Bar::class) instanceof Bar;
    }
}

ref: https://github.com/laravel/framework/blob/5.8/src/Illuminate/Foundation/helpers.php#L107

このヘルパーメソッドを使用した箇所をモックしたい場合、どう書けばよいかというと $this->mock() を使うことでモックが可能になります。具体的にはこのように書くことでモック可能です。

$this->mock(Bar::class);

過去のドキュメントには見当たらなかったのですが、最新であるLaravel ver5.8のドキュメントには記載がありました。

5.8以外でも、確認している限りでは5.7, 5.6のLaravelでもこの方法を取ることが可能です。

Carbon::setTestNow()を使う

now() というヘルパーメソッドがLaravelにはあるのですが、それを使うと Carbon::now() の結果が返ってきます。

ref: https://github.com/laravel/framework/blob/5.8/src/Illuminate/Foundation/helpers.php#L583

しかし、そのときの時間に応じて処理が変わるような場合やnow()をもとにごにょごにょしている場合など、テストケースとして時間を固定しておきたい場合があると思います。そのときに使えるのが Carbon::setTestNow() です。

Carbon Testing Aidsにサンプルとして下記のようなコードがありますが、引数にcarbonのオブジェクトをセットすることで、モックすることが可能になります。

$knownDate = Carbon::create(2001, 5, 21, 12);          // create testing date
Carbon::setTestNow($knownDate);                        // set the mock (of course this could be a real mock object)

またLaravel5.7以前の場合、Carbonは1系の場合がほとんどかと思います。5.8からはCarbonのImmutable版が使えるようなので、より使いやすいものになるのではないかと思います。

参考:
[Laravel5.8] CarbonのImmutable版が使えると何が嬉しいのか

withArgs()を使う

モックしたオブジェクトのメソッドの引数が、さらにオブジェクトになっている場合があります。この場合の引数のテストをしたい場合などに使えるのが withArgs() です。

withArgs() に配列を渡した場合は、単純にその配列の値とモックしたオブジェクトのメソッドに渡される値が合致しているかどうかテストされます。一方で、Closureを渡すこともでき、Closureを渡した場合はその引数をより柔軟にテストすることができます。

仮にBazClassのオブジェクトがhogeというメソッドを実行しており、その引数にpiyoというオブジェクトが渡された場合。

$mock = \Mockery::mock(Baz::class);

$mock->shouldReceive('hoge')->withArgs(function ($piyo) {
    return $piyo instanceof Piyo::class && $piyo->id === 1
});

と書くことができます。このときに、Closureの返り値はbooleanである必要があります。なので、仮に渡された引数が別のクラスのオブジェクトであったり、idプロパティの値が1ではなかった場合はテストが落ちます。

引数にオブジェクトを渡した場合、インスタンスが異なるとテストがコケたりするので、そういう場合によく使います。

また、withArgs(closure) ではなく with(\Mockery::on(closure)) でも同様にテストすることが可能です。

dataProvider()でLaravelのヘルパーを使う

PHPUnitにはデータプロバイダという機能があります。テストメソッドに対して任意の引数を渡すことができます。これで複数のデータパターンを一つのテストメソッドで実行可能になります。

ドキュメントのサンプルでは配列であったりオブジェクトを渡すものが挙げられていますが、Laravelで開発している場合 factory()などでレコードを生成しそのオブジェクトを渡したいといった場合があるかと思います。その場合、Closureを使うことでヘルパーが使えるようになります。具体的には下記のようなコードです。

public function sampleProvider() : array
{
    return [
        'sample1' => function() { return factory(FooEloquent::class); },
        'sample2' => function() { return factory(BarEloquent::class); }
    ]
}

なぜわざわざClosureを使うのかというと、Closureを使わずに実行しようとするとわかります。実際に使わず実行すると下記のようなエラーメッセージが表示されると思います。

Unable to locate factory with name [default] [App\EloquentModels\FooEloquent].

正直深くは追えていないのですが、Closureを使うことでコードの評価タイミングが変わりそれで使用できるようになっているのではと思います。
data providers は ::setUpBeforeClass(), ::setUp() が実行される前に評価されるので、そのままではLaravelの諸々にアクセスできません。Closureを使って遅延評価してやる必要があります。

eval(\Psy\sh())を使う

LaravelにはtinkerというREPLのコマンドがあります。使い方は簡単で、php artisan tinkerと打つだけでREPLが起動します。

さらに具体的な使い方としてよく紹介されているのが php artisan serve でサーバーを起動して、 ソースコード内の任意の箇所に eval(\Psy\sh()); と記述してデバッグする方法だと思います。

eval(\Psy\sh()); はphpunitでテストを実行するときにも使用することができます。テストコードでもアプリケーションコードでも、これが書かれている場所に到達すると、その場所で一度処理が止まり変数の中身などを確認することができテストでデータの流れを確認するときに便利です。

ただ、これを打つのはさすがに面倒なので、僕はPHPStormで簡単に変換できるようショートカットを仕込んでいます。PHPStormのLive Templatesを使用していて、 pry と打ったあとにタブキーを打つと変換されるようにしています。使い方はLive Templates参考にどうぞ。

最後に

他にもテストを書くときに覚えておきたいことは当然ありますが、これは覚えておいたほうが捗るかなというものを挙げてみました🙌

ドキュメントやコードをよく読むと、こんな書き方があったのかとか、そういやこう書くんだった忘れてたみたいなことがよくあります。普段からドキュメントや人のコードをよく読む癖をつけておくことが大事かなと思います。