LoginSignup
7
4

More than 3 years have passed since last update.

【RSpec】メソッドスタブに関するメモ

Last updated at Posted at 2019-08-05

RSpecでメソッドをスタブする方法を整理してみます(expect等でテストするところはまでは言及していません)。
# 色々調べながら試しながら整理してみましたが、イマイチ自信無い感じなのでツッコミ歓迎です..:bow:

サンプルクラス

aki.rb
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 メソッドの引数は任意の文字列で問題ありません。
また、スタブするメソッドのメソッド名等にも制限はありません。

rspec
  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 test

Partial 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

7
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
4