Help us understand the problem. What is going on with this article?

RSpecでスタブ(Stub)を使う

More than 1 year has passed since last update.

こんにちは。seimiyajunです
横浜にあるコワーキングスペースでRuby on Railsのもくもく会を月に1回、2年半くらいやっています
今回はタネマキ Advent Calendar 2018の20日目ということで書きます

タネマキ Advent Calendar 2018自体は1ミリも技術系のアドベントカレンダーではないのですが気にせずいきたいと思います

概要

環境

Ruby 2.5.1
RSpec 3.8.1

やりたいこと

  • 架空のWebサービス microtaskhubをAPI経由で利用し、その結果をAPIで返却する
  • 外部APIを使うメソッドにおいて、create, deleteを実装する
  • RSpecのスタブを使ってテストをスムーズに行う

流れ

  1. createメソッドとdeleteメソッドを書く
  2. テストコードを書く

開発

1. createメソッドとdeleteメソッドを書く

えいや、とこんな感じで。
http_methodというメソッドにparamsを渡せばいい感じにやってくれる前提です

microtaskhub.rb
class Microtaskhub

  def create(username)
    params = {
      method: 'post',
      url: 'https://microtaskhub.net',
      username: username
    }
    response = http_method(params)
    "#{response['header']} : #{response['body']}"
  end

  def destroy(useranme)
    params = {
      method: 'delete',
      url: 'https://microtaskhub.net',
      username: username
    }
    response = http_method(params)
    "#{response['header']} : #{response['body']}"
  end

  private

  def http_method(params)
    # 本来はNet::http等で処理を書く
    {'header' => 200, 'body' => 'Success!'}
  end
end

2. テストコードを書く

Requestが成功した場合

これはcreateについてはそのままテストが書けそうです

microtaskhub_spec.rb
RSpec.describe Microtaskhub do
  describe 'create' do
    context 'request is succeed' do
      it 'returns success message' do
        expect(Microtaskhub.new.create('test')).to eq '200 : Success!'
      end
    end
  end
end

これでも動作はしますが、問題点があります

  • 毎回APIを叩くので速度が遅い (1つだけなら気にならないかもしれませんが、こういったテストが30、100と増えていくと処理時間が問題になります)
  • テストを実行するたびにmicrotaskhubでのユーザー数がどんどん増えていく (ユーザー数に上限があったり、課金ポイントになっているような場合は問題になります)
  • ユニークネームでないと登録できない場合、ランダムな名前でcreateする必要が出てくる (fixtureなどを使えば解決できますが、これは本質ではないですね)
  • microtaskhub側でcreateできない場合、ユーザーを削除のテストができない (招待してメール承認のフローがある場合などはcreateしただけでは削除ができません)

ということでここはstub化しましょう

microtaskhub_spec.rb
RSpec.describe Microtaskhub do
  describe 'create' do
    context 'request is succeed' do
      it 'returns success message' do
        microtaskhub = Microtaskhub.new
        allow(microtaskhub).to receive(:http_method) { {'header' => 200, 'body' => 'test is created'} }
        expect(microtaskhub.create('test')).to eq '200 : test is created'
      end
    end
  end
end

既存コードの http_methodallow(microtaskhub).to receive(:http_method)という記述で上書きしています
処理内容は{}内です。
なお処理が複数行ある場合は下記のようにdo〜endで書くこともできます。

stubをdo~endで書く場合
allow(microtaskhub).to receive(:http_method) do
 #処理内容をここに書く
end

なおdestroyもこんな感じで書けます

microtaskhub_spec.rb
  describe 'destroy' do
    context 'request is succeed' do
      it 'returns success message' do
        microtaskhub = Microtaskhub.new
        allow(microtaskhub).to receive(:http_method) { {'header' => 204, 'body' => 'No Content.'} }
        expect(microtaskhub.destroy('test')).to eq '204 : No Content.'
      end
    end
  end

超重要:実際に対象のサービスがどのような結果を返してくるか、については事前に確認しておくこと

stubの活用例:例外

timeoutを実際に意図的に発生させるのは無理なので、stubに例外を投げてもらいます。

microtaskhub_spec.rb
    context 'timeout' do
      it 'returns timeout message' do
        microtaskhub = Microtaskhub.new
        allow(microtaskhub).to receive(:http_method) { raise Timeout::Error.new }

        expect(microtaskhub.create('test')).to eq '408 : Request Timeout.'
      end
    end

プロダクトコード側では例外をキャッチするようにします
(ここで例外をキャッチするのはすごくいけていないので参考にしないようにお願いします)

microtaskhub.rb
  def create(username)
    params = {
      method: 'post',
      url: 'https://microtaskhub.net',
      username: username
    }

    response = http_method(params)
    "#{response['header']} : #{response['body']}"

  rescue Timeout::Error
    '408 : Request Timeout.'
  end

ここまでのコードをリポジトリにまとめてあります。

改めてタネマキRailsもくもく会

私が主催でRuby on Rails もくもく会@タネマキを月1回開催しています。
初心者歓迎ですので、もしご興味がありましたら参加ください

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away