2019年3月28日木曜日

rspec-test でブロックが渡される関数をスタブする方法

概要

例えば Sidekiq.redis { |c| c.get("hoge") } みたいな使い方をしているときに redis にはアクセスしないでテストしたい場合があると思います
そんな場合に rspec-mocks を使ってブロックをスタブする方法を紹介します

環境

  • macOS 10.14.3
  • Ruby 2.5.1p57

実行コード

例えば以下のような感じで Sidekiq.redis をコールしているとします

require 'sinatra/base'
require './worker.rb'

class Web < Sinatra::Base
  get '/:key' do
    Sidekiq.redis { |c|
      return c.get(params[:key])
    }
  end
end

これを rspec-mocks を使ってスタブするテストを作成します

スタブテストコード

and_yield を使うとブロック内の処理をスタブすることができます

ENV['RACK_ENV'] = 'test'

require './app.rb'
require 'rspec'
require 'rack/test'

describe "Web Tests" do
  include Rack::Test::Methods

  def app
    Web
  end

  it "could be fetch a value from redis" do
    redis = instance_double(Redis, :get => "fuga_test")
    allow(Sidekiq).to receive(:redis).and_yield(redis)
    get '/hoge'
    expect(last_response.body).to eq "fuga_test"
  end
end

これでテストすると Redis にはアクセスすることなくテストできます

応用

同じメソッド名で引数の key が異なる呼び出しをブロック内で複数しているとします
その場合は引数の key を正規表現を使って判断し引数に応じて別々のレスポンスを返却するように stub を定義します

require 'sinatra/base'
require './worker.rb'

class Web < Sinatra::Base
  get '/:key' do
    Sidekiq.redis { |c|
      if c.get("fuga") == "fuga_test"
        return c.get(params[:key])
      end
    }
  end
end
ENV['RACK_ENV'] = 'test'

require './app.rb'
require 'rspec'
require 'rack/test'

describe "Web Tests" do
  include Rack::Test::Methods

  def app
    Web
  end

  it "could be fetch a value from redis" do
    redis = instance_double(Redis)
    allow(redis).to receive(:get).with(/hoge/).and_return("hoge_test")
    allow(redis).to receive(:get).with(/fuga/).and_return("fuga_test")
    allow(Sidekiq).to receive(:redis).and_yield(redis)
    get '/hoge'
    expect(last_response.body).to eq "hoge_test"
  end
end

allow(redis).to receive(:get).with(/hoge/).and_return("hoge_test") を引数の key が異なる呼び出しごとに定義しています
これで c.get("fuga") の場合は fuga_test が返り c.get("hoge") の場合は hoge_test が stub から返るようになります

参考サイト

0 件のコメント:

コメントを投稿