RSpecでメソッドをスタブする方法を整理してみます(expect等でテストするところはまでは言及していません)。
# 色々調べながら試しながら整理してみましたが、イマイチ自信無い感じなのでツッコミ歓迎です..
サンプルクラス
class Aki
def self.test_aki
"test_aki"
end
def test
"instance test"
end
end
方向性
スタブには allow
メソッドを使うことになりますが、大きな分類として、以下があるように思いました。
- Doubleオブジェクトにメソッドを追加し、そのメソッドをスタブする
-
double
メソッド -
class_double
メソッド
-
- 実際のリソースをスタブする
- オブジェクトのメソッドをスタブする
- クラスのメソッドをスタブする
Doubleオブジェクトにメソッドを追加し、そのメソッドをスタブする
double
メソッド(空のモックオブジェクトにメソッドをスタブする)
単純な double
メソッドを使う場合です。これはDoubleオブジェクトを作成します。Doubleお弁当は空のモックオブジェクトで、任意のメソッド名のスタブを作れます。
こちらは以下の通り空のモックオブジェクトになるので、5行目の allow
で偽のメソッドをスタブします。
ちなみに出来上がったオブジェクトは実際の Aki
クラスに紐付いているわけでは無く、 double
メソッドの引数は任意の文字列で問題ありません。
また、スタブするメソッドのメソッド名等にも制限はありません。
it "double" do
aki_double = double("aki")
pp aki_double # => #<Double "aki">
pp aki_double.class # => <Double "aki">
allow(aki_double).to receive(:test_aki).and_return("stub test")
allow(aki_double).to receive(:test_aki_hoge).and_return("stub test") # 実際には実装されていないメソッド名もスタブ化可能
pp aki_double.test_aki # => "stub test"
end
参考リンク
Test Doubles - Basics - RSpec Mocks - RSpec - Relish https://relishapp.com/rspec/rspec-mocks/docs/basics/test-doubles
class_double
メソッド
class_double
を用いる場合のサンプルは以下の通りです。
基本的には上記 double
と似たようなイメージですが、 class_double
を用いると、実際のクラスの情報と紐付いて実際のクラスに実装されていないメソッドのスタブ化はできないようになります。
ただし、以下の場合は、その実行環境全体でそのクラスに対してスタブが効いたわけではなく、あくまで class_double
の返り値で allow
したオブジェクトのみスタブされている(下から3行目の通り、普通にクラスメソッドを呼んだ場合は、実際のメソッドが実行されている)。
it "class_double" do
pp Aki # Aki
aki_double = class_double("Aki")
pp aki_double.is_a?(Object) # => true
pp aki_double.is_a?(Class) # => false
pp Aki # Aki
pp Aki.test_aki # => test_aki
pp aki_double # => #<ClassDouble(Aki) (anonymous)>
# pp aki_double.test_aki # まだメソッドをスタブ化していないため、これはエラーになる => :<ClassDouble(Aki) (anonymous)> received unexpected message :test_aki with (no args)
# スタブ
allow(aki_double).to receive(:test_aki).and_return("stub test")
# allow(aki_double).to receive(:test_aki_hoge).and_return("stub test") # 実際には存在しないメソッド名のためエラーとなる => the Aki class does not implement the class method: test_aki_hoge
# クラス
pp Aki.test_aki # => test_aki
pp aki_double.test_aki # => "stub test"
end
参考
Using a class double - Verifying doubles - RSpec Mocks - RSpec - Relish https://relishapp.com/rspec/rspec-mocks/docs/verifying-doubles/using-a-class-double
as_stubbed_const
オプション
上記の場合は、その実行内のすべてのクラスに対してスタブされたわけでは無かったのだが、 class_double
する際に as_stubbed_const
オプションを付与するとクラス(の定数というのだろうか?)自体をスタブできる。
it "class_double as_stubbed_const" do
pp Aki # Aki
Aki.test_aki # test_aki
aki_double = class_double("Aki").as_stubbed_const
allow(aki_double).to receive(:test_aki).and_return("stub test") # allowしないと `test_aki` メソッドは定義されていないものとしてエラーとなる
pp aki_double # #<ClassDouble(Aki) (anonymous)>
pp Aki # #<ClassDouble(Aki) (anonymous)> ★as_stubbed_constをすると定数としてのクラスを置き換える
pp Aki.test_aki # stub test
end
実際のリソースをスタブする
実際のオブジェクトのメソッドをスタブする
実際のクラスのオブジェクトに対しても allow
を実施し、メソッドをスタブ出来ます。この場合は、Doubleの時と違い、実際に無いメソッド名のメソッドはスタブ化できません。
it "Allow(Object)" do
aki = Aki.new
pp aki # #<Aki:0x0000563629e5f848>
pp aki.test # => "instance test
# これだけでは影響なし
allow(aki)
pp aki # => #<Aki:0x0000563629e5f848>
pp aki.test # => "instance test
# スタブ(返り値なし)
allow(aki).to receive(:test)
pp aki.test # => nil
# スタブ(返り値あり)
allow(aki).to receive(:test).and_return("stub test") # 実際に存在するメソッドだから大丈夫
# allow(aki).to receive(:test2).and_return("testtest") # 存在しないメソッドだからダメ… #<Aki:0x000055a896306c88> does not implement: test2
pp aki # #<Aki:0x0000563629e5f848> オブジェクト自体は変わっていない
pp aki.test # => stub test"
end
ちなみにこの方法は Partial test doubles
というらしいです(日本語だと何と呼ぶのがいいのだろう)。
A partial test double is an extension of a real object in a system that is instrumented with
test-double like behaviour in the context of a testPartial test doubles - Basics - RSpec Mocks - RSpec - Relish https://relishapp.com/rspec/rspec-mocks/docs/basics/partial-test-doubles
この方法はSpyという方法で、メソッドが呼ばれたかを検証するのによく使われると思います。
Spies - Basics - RSpec Mocks - RSpec - Relish https://relishapp.com/rspec/rspec-mocks/v/3-8/docs/basics/spies#spy-on-a-method-on-a-partial-double
rspec3でメソッドが呼び出されたかテストする - Qiita https://qiita.com/_am_/items/398b0782fa3754ff3878
実際のクラスメソッドをスタブする
実際に存在するクラスに対しても実施可能です。こちらも実際に無いメソッドのスタブは出来ません。
it "Allow(Class)" do
pp Aki # => Aki
pp Aki.test_aki # => "test_aki"
allow(Aki).to receive(:test_aki).and_return("stub test") # 実際に存在するメソッドだから大丈夫
# allow(Aki).to receive(:test2).and_return("testtest") # 存在しないメソッドだからこの時点で怒られる Aki does not implement: test2
pp Aki # => Aki
pp Aki.test_aki # => "stub test"
end
Allowing messages - Basics - RSpec Mocks - RSpec - Relish https://relishapp.com/rspec/rspec-mocks/v/3-8/docs/basics/allowing-messages
おわり
ほしい:
Qiita > 要望 > コード左側に行番号を表示 - Qiita https://qiita.com/7of9/items/e65f2f36c817ab965284
その他参考
使えるRSpec入門・その3「ゼロからわかるモック(mock)を使ったテストの書き方」 - Qiita https://qiita.com/jnchito/items/640f17e124ab263a54dd