Tech Sketch Bucket of Technical Chips by TIS Inc.

RSpec Tips -- 引数をチェックする方法まとめ --

Pocket

この記事はeXcale Developer's Blogから移転されたものです。


eXcale 開発チームの冨樫です。

Ruby のテストフレームワークで人気の RSpec について、ハマりそうな点や知っておくと便利な機能をご紹介致します。
今回は「引数をチェックする方法まとめ」です。 should_receive を使用したチェック方法と、チェック対象のメソッドが複数回呼ばれる場合の問題とその問題の解決方法をご紹介致します。


eXcaleでは期間限定でサインアップキャンペーン実施中です。
キャンペーン内容についてはこちらを参照してください。


今回ご紹介する内容の題目です

  • should_receive で引数をチェックする
  • メソッドが複数回実行された際、指定した引数で呼ばれたかを確認する

should_receive で引数をチェックする

should_receive には引数をチェックするための機能が豊富にあるため、大抵のケースで問題無く使用することができます。引数のチェック方法について、下記に例を記載するのでご確認ください。

・引数はなんでも良く、メソッドが呼ばれることを確認
target_obj.should_receive(:target_method).with(:any_args)
target_obj.should_receive(:target_method) # withを省略することも可能です。

・引数なしで呼ばれることを確認
target_obj.should_receive(:target_method).with(:no_args)

・指定した引数で呼ばれることを確認
target_obj.should_receive(:target_method).with('target_str')
target_obj.should_receive(:target_method).with('target_str', 100) # 複数指定も可能です
target_obj.should_receive(:target_method).with(hoge: 'hoge', fuga: 'fuga') # ハッシュを指定した場合

・指定した型で呼ばれることを確認
target_obj.should_receive(:target_method).with(:string)
target_obj.should_receive(:target_method).with(:numeric)
target_obj.should_receive(:target_method).with(:boolean)

・何かしらの引数で呼ばれていることを確認
target_obj.should_receive(:target_method).with(:anything)
target_obj.should_receive(:target_method).with(:anything, 100) # 複数指定も可能です。

・引数がハッシュで、指定したハッシュが含まれているを確認
target_obj.should_receive(:target_method).with(hash_including(hoge: 'hoge'))
target_obj.should_receive(:target_method).with(hash_including(:hoge)) # キーのみの指定も可能です
target_obj.should_receive(:target_method).with(hash_including(:hoge, :fuga)) # 複数指定も可能です。

・引数がハッシュで、指定したハッシュ値が含まれていないことを確認
target_obj.should_receive(:target_method).with(hash_not_including(hoge: 'hoge'))
target_obj.should_receive(:target_method).with(hash_not_including(:hoge)) # キーのみの指定も可能です
target_obj.should_receive(:target_method).with(hash_not_including(:hoge, :fuga)) # 複数指定も可能です。

・引数がオブジェクトで、指定したメソッドが含まれているを確認
target_obj.should_receive(:target_method).with(duck_type(:hello))
target_obj.should_receive(:target_method).with(duck_type(:length, :each)) # 複数指定も可能です


メソッドが複数回実行された際、指定した引数で呼ばれたかを確認する

引数をチェックする場合、基本的には上記に記載した方法で対応できますが、
指定した引数で呼ばれたかを確認する場合でも、メソッドが複数回実行された際には注意が必要です。

should_receive().with() で指定した場合、一度でも条件に一致しない引数でメソッドが呼ばれると、その時点でテスト失敗の判定となるためです。

ここでは、上記のような、条件に一致しない引数でメソッドが呼ばれることがある場合でも、指定した引数でメソッドが呼ばれたかをテストする方法をご紹介致します。

まずはメソッドが1回だけ呼ばれる場合として 「AWS::SimpleEmailService で1件メールを送信する処理」を例に、実装してみましょう。

まずはテストコードから。
下記は「指定したメールアドレスにメールが送信されているかをチェック」するテストコードです。

まず send_email メソッドに渡されるハッシュの「:to」 に 'hoge@example.com' が指定されていることをチェックしたいので、そのチェックを行うための mock を作成し(2〜5行目)、次にAWS::SimpleEmailService の new メソッドを stub化し、戻り値が作成した mock になるようにしています。(6行目)。

では実装です。
引数で渡されたメールアドレスにメールを送信する処理を実装します。

メールを送信するためには、 AWS::SimpleEmailService の send_email メソッドを実行するだけで良いので、特に問題となる箇所は無いかと思います。
テストを実行して、正常終了することを確認してください。

続いて、メソッドが複数回呼ばれる場合として「AWS::SimpleEmailService で複数箇所にメールを送信する処理」を例に、実装していきましょう。

まずはテストコードから。
テスト内容は1個所の時と同様で「指定したメールアドレスにメールが送信されているかをチェック」です。
ただし、メールアドレスが複数あるため、1個所の時に使用した下記の mock では 'hoge@example.com' 以外のメールアドレスが使用された際にテストが失敗してしまいます。

これを回避するため should_receive を使わず、引数を自分で取得し、
取得した中に指定した値が含まれているかでチェックします。

下記のテストコードを見てみましょう。

注目は5行目からで、receive の処理が無くなり、変わりに ses.send_email のメソッドをテスト側で定義し、引数で渡されたハッシュの「:to」の値を全て保持するようにしています。

値を全て保持してしまえば、後は単純に保持した値の中に指定した値が含まれるかチェックするのみです(11行目)

では実装です。
下記の実装を見てください

1個所の時の実装を元に、4〜8行目にsend_mailメソッドを複数回実行させるメソッド send_mail_all_user を追加しているのみです。
テストを実行して、正常終了することを確認してください。

このように、sould_receive では対応が難しい場合は、テスト側でメソッドを定義して引数を保持してしまうのも有効です。


最後に

今回は 引数をチェックする方法について、基本的なチェック方法と、少し複雑なケースでのチェック方法をご紹介させて頂きました。
テストコードは面倒ですが怠けてるとそのうち自分に返ってくるので、便利機能を駆使して負けじと書いて行きましょう。

テストがお済みのアプリがありましたら、是非 eXcale へデプロイしてください。
操作手順は簡単なのでお気軽にお試し頂けます(基本無料です)。

※ サインアップからデプロイまでの流れは こちらのブログ で手順を紹介しているので、ご参照ください。

エンジニア採用中!私たちと一緒に働いてみませんか?