やる気がストロングZERO

やる気のストロングスタイル

RSpecでmockを使ってテストする

こういう時にmockを使う

あるメソッドのテストコードを書きたい。
そのメソッドは内部で外部システムと通信をするclass(以下apiクラスと呼ぶ)を使っているため、実行のたびに結果が異なるのでテストがかけない。
apiクラスが返してくる値を決めてしまいたい。←コレをmockで解決する

前提コード

MyClass#some_infoをテストしたいが、ApiClass#some_dataがAを返すかBを返すかが状況で変わってくるのでテストできない。

class ApiClass
  def some_data()
    # 外部apiにアクセスしてデータを取ってくる(”A" or “B” を返す)
  end
end

class MyClass
  def initialize
    @api = ApiClass.new
  end

  def some_info
    data = @api.some_data
    case data
    when "A"
        return "aでした"
    when "B"
        return "bでした"
    end
  end
end

ApiClassのmockを作ってテストする

describe 'moc練習' do
  before do
    @api_moc = double("ApiClass moc") # ApiClassのインスタンスのmocを作成
  end

  context "ApiClass#some_dataがAを返す場合" do
    before do
      allow(@api_moc).to receive(:some_data).and_return("A") #@api_moc.some_data()が"A"を返すように設定
      allow(ApiClass).to receive(:new).and_return(@api_moc) #ApiClass.newがapi_mocを返すように設定
    end

    it '[aでした]が返る' do
      my_class = MyClass.new
      expect(my_class.some_info).to eq "aでした"
    end
  end

  context "ApiClass#some_dataがBを返す場合" do
    before do
      allow(@api_moc).to receive(:some_data).and_return("B") #@api_moc.some_data()が"B"を返すように設定
      allow(ApiClass).to receive(:new).and_return(@api_moc) #ApiClass.newがapi_mocを返すように設定
    end

    it '[bでした]が返る' do
      my_class = MyClass.new
      expect(my_class.some_info).to eq "bでした"
    end
  end
end

他にも、、

expect(@api_moc).to receive(:some_data) でsome_dataが呼び出されなければNGとしたり、

allow(@api_moc).to receive(:some_data).and_raise('エラーが発生しました’)
で例外を発生させたり、

呼び出し回数をチェックしたり、
引数をチェックしたりもできるらしい。

参考)

使えるRSpec入門・その3「ゼロからわかるモック(mock)を使ったテストの書き方」 - Qiita