テストのダブル?スタブ?モック?スパイ?
とりあえずRSpec
bundle exec rspec -v
=> 3.5.1
おそらく、次のように定義できるっぽい、が人によって定義がさっぱり違うらしいので注意
- ダブル => スタブ、モック等の「何かの代わりに仕事をさせる為に仕込むモノ」の総称
- スタブ => ダミーのメソッド(メソッドスタブ)を追加されたダブル(もしくは既存のオブジェクト)
- モック => ダミーのメソッド(メソッドスタブ)を追加されると同時に、それが呼ばれる事をチェックされるように登録したダブル(もしくは既存のオブジェクト)
- スパイ => モック(then)を when の後に書けるようにしたモック(詳しくは後述)
・・が、めんどくさいので全部「モック」と読んでいる人が多い気がする
ダブルを作ってみる
double
#<Double (anonymous)>
# 引数で適当な名前を付けられる。付けなくてもいいが、エラーメッセージに表示されるので、付けたほうがいいでしょう
double('tekitou-na-namae')
=> #<Double "tekitou-na-namae">
スタブを作ってみる
# doubleに対して、true を返す is_homo? スタブメソッドを定義している
moko = double('dummy_object')
allow(moko).to receive(:is_homo?).and_return(true)
# 既存のオブジェクト(Array)に対して、99 を返す first スタブメソッドを定義している
user_ids = [1, 2, 3, 4, 5]
allow(user_ids).to receive(:first).and_return(99)
他にも色々なスタブ
# 戻り値がnil
allow(user_ids).to receive(:first)
# ブロックの戻り値がスタブメソッドの戻り値になる
allow(user_ids).to receive(:first) { 99 }
# Mokoクラスのインスタンスにスタブメソッドを定義する
allow_any_instance_of(Moko).to receive(:first).and_return(99)
# エラーをraiseさせる
allow(user_ids).to receive(:first).and_raise(RuntimeError.new('HageException'))
モックを作ってみる(スタブの allow を expect に変えただけ説)
# doubleに対して、true を返す is_homo? スタブメソッドを定義している
# この is_homo? メソッドが呼び出されなければエラーとなる
moko = double('dummy_object')
expect(moko).to receive(:is_homo?).and_return(true)
# 既存のオブジェクト(Array)に対して、99 を返す first スタブメソッドを定義している
# この first メソッドが呼び出されなければエラーとなる
user_ids = [1, 2, 3, 4, 5]
expect(user_ids).to receive(:first).and_return(99)
他にも色々なモック
# 戻り値がnil
expect(user_ids).to receive(:first)
# ブロックの戻り値がスタブメソッドの戻り値になる
expect(user_ids).to receive(:first) { 99 }
# Mokoクラスのインスタンス全てにスタブメソッドを定義する
expect_any_instance_of(Moko).to receive(:first).and_return(99)
更に色々な書き方とかマッチャとか
# 呼ばれる毎に違う返答をする(例えば、コネクトリトライのテスト辺りに便利??)
# 4回目に呼ばれると最後の値を返答する
allow(db).to receive(:connect).and_return(nil, nil, 'connect_object')
# モックの場合には、3回のみ呼ばれる事をチェックする(4回めの呼び出しでエラー)
expect(db).to receive(:connect).and_return(nil, nil, 'connect_object')
# きっかり1回呼ばれる事を期待する
# スタブ(allow)であっても、しっかりチェックされる
expect(db).to receive(:connect).and_return('a').exactly(1).times
expect(db).to receive(:connect).and_return('a').once
# きっかり2回呼ばれる事を期待する
# スタブ(allow)であっても、しっかりチェックされる
expect(db).to receive(:connect).and_return('a').exactly(2).times
expect(db).to receive(:connect).and_return('a').twice
# 最大2回呼ばれる事を期待する
# 3回以上呼ばれるとエラー
# スタブ(allow)であっても、しっかりチェックされる
expect(db).to receive(:connect).at_most(2).times
# 2回以上呼ばれる事を期待する
# 1回しか呼ばれないとエラー
# スタブ(allow)であっても、しっかりチェックされる
expect(db).to receive(:connect).at_least(2).times
# 呼ばれない事を期待する
# 1回でも呼ばれたらエラー
expect(db).to_not receive(:connect)
expect(db).to receive(:connect).never
expect(db).to receive(:connect).exactly(0).times
# 引数を期待する
# スタブの場合には、正しい引数で呼ばれる事をチェックする(引数が違うとエラー)
# 加えて、モックの場合には呼び出されなかった場合にもエラー
allow(db).to receive(:connect).with(1)
expect(db).to receive(:connect).with(1)
expect(db).to receive(:connect).with(1, 'homo')
# 引数の型
# 引数が1
expect(db).to receive(:connect).with(1)
# 引数がFixnum
expect(db).to receive(:connect).with(instance_of(Fixnum))
# 何でもいい
expect(db).to receive(:connect).with(anything())
# 何でもいい 数も気にしない
expect(db).to receive(:connect).with(any_args())
# 引数なし
expect(db).to receive(:connect).with(no_args())
# Hashを含んでいる
expect(db).to receive(:connect).with(hash_including(a: 1, b: 2))
# Hashを含んでいない
expect(db).to receive(:connect).with(hash_not_including(a: 1, b: 2))
# 正規表現
expect(db).to receive(:connect).with(/\Ahomo\z/)
# 複合
expect(db).to receive(:connect).with(/\Ahomo\z/, instance_of(Fixnum))
# moko.echo にて RuntimeError が raise される事を期待
expect{ moko.echo }.to raise_error(RuntimeError)
# 呼ばれる順番を指定 (echo1, echo2 の順番にて呼び出されることを期待)
expect(moko).to receive(:echo1).ordered
expect(moko).to receive(:echo2).ordered
# moko.inclement を呼び出すと、 moko.num の結果が 1 から 2 になる事を期待
expect{ moko.inclement }.to change{ moko.num }.from(1).to(2)
# moko.inclement を呼び出すと、 moko.num の結果が 1 増える事を期待
expect{ moko.inclement }.to change{ moko.num }.by(1)
# 含んでいる事を期待
expect([1,2,3]).to include(1)
# 5 のプラマイ2 である事を期待
expect(moko.num).to be_within(2).of(5)
#hould_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(duck_type(:hello))
スパイ
使わなくていいです!!