RSpecでモックを使う

f:id:utouto97:20210725220655j:plain

RSpecでモックオブジェクトを作る

doubleでモックを作る

RSpecでモックオブジェクトを作成するには、doubleを使います。

mock_a = double(A)

↑のようにすることで、モックオブジェクトを作成することができます。

スタブでメソッドを付与

doubleで作ったモックは、メソッドをもっていないので、スタブを使って仮のメソッドを設定してあげます。

allow(a).to receive(:f).and_return(1234)

RSpecでモックを試してみる

次のようなクラスAとクラスBを準備します。

class A
  def doSomething
    b = B.new
    b.getCount()
  end
end

class B
  def getCount()
    pp "called B.getCount()"
    rand()
  end
end

まずは、モックを利用せずにテストを書きます。

RSpec.describe A do
  context "モックを利用しない場合" do
    example "getCountが呼び出される" do
      a = A.new
      expect(a.doSomething()).to eq 0
    end
  end
end

結果は次のようになりました。

"called B.getCount()"
F

Failures:

  1) A モックを利用しない場合 getCountが呼び出される
     Failure/Error: expect(a.doSomething()).to eq 0
     
       expected: 0
            got: 0.29231320169185815
     
       (compared using ==)
     # ./spec/calc_spec.rb:24:in `block (3 levels) in <top (required)>'

Finished in 0.01401 seconds (files took 0.07903 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/calc_spec.rb:18 # A モックを利用しない場合 getCountが呼び出される

"called B.getCount()"の文字列が表示されており、クラスBのメソッドgetCount()が呼び出されていることが確認できます。
これは、文字列を表示しているだけですが、例えば、ツイートを投稿するようなメソッドの場合、テストのたびにツイートされていては大変なことになります。
また、返り値はrand()により取得しているため、テストするのが難しいです。

モックを使って書くと次のようになります。

RSpec.describe A do
  context "モックを利用" do
    example "getCountが呼び出される" do
      mock_b = double(B)
      allow(B).to receive(:new).and_return(mock_b)
      allow(mock_b).to receive(:getCount).and_return(0)

      a = A.new
      expect(a.doSomething()).to eq 0
    end
  end
end

結果は↓のようになります。

.

Finished in 0.00508 seconds (files took 0.07579 seconds to load)
1 example, 0 failures

実際のクラスBではなく、かわりのもの(モック)がAの中で利用されています。

また、次の1行をテストの末尾に加えることで、mock_bgetCount()メソッドが1回呼ばれたことをテストできます。

expect(mock_b).to have_received(:getCount).once

exactly(n).timesを使うことで、n回呼ばれたことをテストできます。

expect(mock_b).to have_received(:getCount).exactly(n).times

終わり