helen's blog

ずっとおもしろいことしてたいな。

(続)railsでモック使ってみた話

helen.hatenablog.com
一晩でさっくり書いたものの追記、修正。

モック・スタブとは
  • 単体テストで必要になるパーツを擬似的に再現する仕組み
使われるところ
  • 揃っていないため「本物」でテストできない時
  • 状況によって変化するオブジェクトがテスト結果に影響する時
モックとスタブの違い
  • モック
    • 「オブジェクトのメソッドがどう呼ばれて何を返すか」というインタフェースも含めたテストのために使う
  • スタブ
    • テストをスムーズに行うために「あるオブジェクトのメソッドが呼ばれたら、ある戻り値を返す」ために使う
スタブ

RSpectの2系までは以下のようにスタブを使っていました。

obj.stub(:foo).and_return(15)
obj.should_receive(:bar)

3系からはこれらがOld Syntax扱いになり、
動きますがconfigに設定を書かないとwarningが出ます
RSpec4では他のgemに切り出されるかもしれません。
Old syntax - RSpec Mocks - RSpec - Relish

一応構文としては、

オブジェクト.stub(:メソッド ).and_return( 戻り値 )
#オブジェクト.メソッドで戻り値が返される

こんな感じですが今回使わないので放置します

書き直してみる

HogeClassから未実装の外部のクラス(OutClass)のメソッド(find_by_id)を呼ぶため
OutClassをモックにしてfind_by_idを呼べるようにします。

# spec
before do
  @out_class_mock = double("OutClass mock") # モック作成
  # OutClass#find_by_idが呼ばれたら@out_class_mockを返す
  allow(OutClass).to receive(:find_by_id).and_return(@out_class_mock) 
end

let(:hoge){ Hoge.find(1) }

it "HogeIDに基づき取得した情報が正しいこと" do
  # モックが返されているか確認する
  expect(hoge.find_data(id: 1)).to eq @out_class_mock
end

# HogeClass
def find_data(id:, from: 0, size: 20)
  @find_data = OutClass.find_by_id(id: id, from: from, size: size)
end

# OutClass
# 外部クラスも用意だけしておく
class << self
   def find_by_id(id:, from: 0, size: 20)
end

ここで鬼軍曹のありがたいお言葉が

モックの掟

モックを使ったテストでは、以下の確認が基本要素となる(と自分は思っている)ため、これをクリアすると一通りになるかと。
・呼び出し先が正しいこと -> OK(そもそもモック化したメソッドで無いとこける)
・呼び出す際の引数が正しいこと -> ???
・戻り値の使い方が正しいこと -> OK( .and_return した結果を返していることを検証)

というわけで引数の検証します

引数の検証の仕方
expect(オブジェクト).to receive(メソッド).with(引数).and_return(戻したいオブジェクト、値)

# 例:hoge_mock#helloが引数としてworldを受け取ることを検証
# worldを受け取ったらreturn successを返します
expect(hoge_mock).to receive(:hello).with('world').and_return("return success")

というわけで引数をテストするspecを追加しました

it "find_by_idを呼び出す際の引数が正しいこと" do
 expect(OutClass).to receive(:find_by_id) # ここでallowすると不適切な引数でもテスト成功してしまう
   .with(id: 1, from: 0, size: 20) # withでキーワード引数指定できる
   .and_return(@out_class_mock) # モックを返します

  expect(hoge.find_data(id: 1)).to eq @out_class_mock
end

ちなみに

@out_class_mock = double("OutClass mock") # モック作成
p @out_class_mock
# 出力するとdoubleで指定したものが表示されます
#<Double "OutClass mock">

参考
rspec-mocks (3.3.2)
RSpec Mocks 3.4 - RSpec Mocks - RSpec - Relish
RSpec でテストを作るのに役立つ「モック/スタブ」のシンプルな説明 - 酒と泪とRubyとRailsと
Matching arguments - Setting constraints - RSpec Mocks - RSpec - Relish