はじめに

OPENLOGI Advent Calendar 2017 の3日目です。

弊社では、会社名と同じオープンロジという在庫管理サービスを作っています。

単体で在庫管理ができるのに加えて、APIを通じて他社の受発注管理サービスと連携したり、ネットショップと連携したりしてます。
(例えば、A社とB社でネットショップを開設して、その売上をC社の受発注管理システムで管理して、売り上げた商品の発送/在庫管理はオープンロジを利用して。。。という具合に。)

開発にあたって、弊社では主にmockeryを使って、本番のAPIにアクセスしないようにテストコードを書いています。

ただMockeryというのは、ネットをつまみ食いしても、わかんない人には結構ハードルが高いような気がします。
そこで、この記事ではメール送信を例にして、Mockeryを有能な新人に見立てて「Mockeryくん is Who?」をちょっとでも説明できればと思います。

ちなみに↓のことを知ってたら、Mockeryくんのことを理解しやすいかもしれません。

  • php
  • Laravel
  • PHPUnit

Mockeryくん is Who?

Mockery is a simple yet flexible PHP mock object framework for use in unit testing with PHPUnit, PHPSpec or any other testing framework. (by公式)
だそうです。べた褒めですねー。

僕は もしも、気軽に利用できない外部サービス(的なもの)を利用できたら を実現するために 先輩(=外部サービス)の仕事を奪うやつ がMockeryくんだと思います。

  • もしも(誰かに送信されたら困るから)気軽に利用できない「メール」を利用できたら
  • もしも(向こうの会社に怒られるから)気軽にAPIアクセスできない「他社の受発注サービス」にAPIアクセスできたら

1つめが実現できれば、いくらメール送信しても、誰にも迷惑がかからないですね。
2つめが実現できれば、いくら fetch して売上データを取っても、相手サーバーに負荷をかけませんね。

便利ですねー。
じゃあ、実際にどうやってそんな都合のいいものを作っていくのか?
メール送信を例にしてみてみましょう。

Mail::sendさん(頑張り屋の先輩)

Mail::send(
    'emails.ほげほげ',
    '変数A',
    function ($message) use ($to, $bcc) {
        $message->to($to)->subject('入庫のご連絡')->bcc($bcc);
    }
);

1つめのemails.ほげほげ はbladeテンプレートです。要するにかっこいいHTMLです。
2つめの変数Aです。1つめのemails.ほげほげ入庫された商品は{変数A}ですが書いてあるので、そこに入れるやつです。
3つめのfunction ~は見ての通りです。宛先とか色々きめます。

このコードを入庫完了処理の後ろの方に書いておけば、お手軽にメール送信までしてくれます。
Mail::send先輩は確固たるアウトプットを出してくれますねー。

ただ、頑張り屋さんすぎるために、いちいちメール送信をするのが困ったところです。
そこで有能新人、Mockeryくんの出番です。

Mockeryくん

Mail::shouldReceive('send')
            ->once()
            ->with(
                'emails.ほげほげ',
                Mockery::on(function ($data) {
                    $this->assertArrayHasKey('変数A', $data);
                    // メール文面確認
                    $content = view('emails.ほげほげ', $data)->render();
                    $this->assertContains('ハンドスピナーが入庫されました', $content);

                    return true;
                }),
                Mockery::on(function (\Closure $closure) {
                    $mock = Mockery::mock('Illuminate\Mailer\Message');
                    $mock->shouldReceive('to')->once()->andReturn($mock);
                    $mock->shouldReceive('bcc')->once()->andReturn($mock);
                    $mock->shouldReceive('subject')->once()->with('入庫のご連絡')->andReturn($mock);
                    $closure($mock);

                    return true;
                })
            );

なんかややこしいですねー。
ですが、こいつはすごい仕事ができるやつです!
結論から言えば、実際にメールを送信せずに、文面のチェックに加えて、宛先とかをチェックしてくれるんです。
じゃあどういうことか、上から順番に見ていきましょう。

Mail::shouldReceive('send')

「Mailのsendは僕が代わりにやるよ」と言ってます。
Mail::send先輩から、仕事を引き受けてます。
新人の鑑ですねー。

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

ちなみに、こいつは仕事ができないやつです。
仕事を引き受けたくせに、「OK!」としか言わないので、テストが全然できなくなっちゃいます。

->once()

「僕が引き受けるMailのsendは1回だけですよね?」と言っています。
自分がどこまで担当するか明確にしてるんですねー。
なので、Mail::sendが2回呼ばれたら「話が違う」とエラーを返します。

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

ちなみに、こいつは仕事を引き受けたくせに、「その仕事は1回もやらないだろう」と決めうってます。
なので、1回でもMail::sendが呼ばれたら「話が違う」とエラーを返します。
すごい根性してますよねー。

->with()

「AさんとBさんとしか仕事しないよ」と言ってます。
普段、Mail::sendは'emails.ほげほげ''変数A'function ~と仕事してると知って、「その人たち以外と仕事はしない」と言ってます。
例えば、'emails.ふがふが'が渡ってきたら、エラーを返します。

あくまで、MockeryくんはMail::sendさんと同条件を望んでいるわけですねー。
余計な自分らしさを出さない仕事人ですねー。

Mockery::on(function ~

「Aさん、本当に仕事してますか?」と言ってます。

$this->assertArrayHasKey('変数A', $data);

もし、Aさんが自分の想定した$dataに存在しない場合にエラーを返します。

他の人の批判までする素晴らしいやつですねー。

>andReturn()

「仕事のアウトプットはこれですよね」というのを言ってます。
今回はメール送信はしたくないため「アウトプットはいりませんよね?」というわけなので、記述がありません。

常に仕事の成果に目を向ける、本当にできるやつですねー。

Mockeryくん(最終形)

「先輩の仕事を奪うくせに、仕事の回数を指定して、チームメンバーを限定して、そのメンバーの仕事をチェックする」
という、有能すぎて現実にいたら間違いなく誰も関わりたがらないやつになりました。
ですが、プログラミングの世界ではこんなできるやつはいません。

今回の例の、メール送信でもかなり有能でしたが、外部APIのMockeryになれば尋常じゃなく有能になります。
どれだけ有能かはあなたの目で確かめてください!

最後に

気がついたら、かなりお気楽なMockey紹介になってしまいましたが、初心者の方のちょっとでも手助けになれば嬉しいです。

参考

https://github.com/mockery/mockery