🕷️

【RSpec】Webmockを使ってテストをモックする

2022/10/28に公開

TL;DR

webmockを使って外部APIからデータをフェッチするテストを書いたら便利という話です。
https://github.com/bblimke/webmock

Webmock

外部APIからデータをフェッチするクラスの単体テストを書きたい。そんなことってよくありますよね。

そんな時にwebmockというgemが使えるようです。

今回は、json placeholderからフェッチした値をテストしながらwebmockについて学んでみたいと思います。

概念を理解することを目的としているので、rspecの準備や環境を省いている点はご了承ください。

# model/Jsonplaceholder.rb

require 'net/https'
require 'uri'

class Jsonplaceholder < ApplicationRecord

  def self.access(endpoint)
    begin
      uri = URI("https://jsonplaceholder.typicode.com/#{endpoint}")
      response = Net::HTTP.get_response(uri)
      puts response.code
    rescue => e
      puts e
    end
  end
end

こちらのクラスを実行すると、下記のようなデータが返ってきます。

#fixture/jsonplaceholder/response.json
[
  {
    "id": 1,
    "name": "Leanne Graham",
    "username": "Bret",
    "email": "Sincere@april.biz",
    "address": {
      "street": "Kulas Light",
      "suite": "Apt. 556",
      "city": "Gwenborough",
      "zipcode": "92998-3874",
      "geo": {
        "lat": "-37.3159",
        "lng": "81.1496"
      }
    },
    "phone": "1-770-736-8031 x56442",
    "website": "hildegard.org",
    "company": {
      "name": "Romaguera-Crona",
      "catchPhrase": "Multi-layered client-server neural-net",
      "bs": "harness real-time e-markets"
    }
  },
  {
    "id": 2,
    "name": "Ervin Howell",
    "username": "Antonette",
    "email": "Shanna@melissa.tv",
    "address": {
      "street": "Victor Plains",
      "suite": "Suite 879",
      "city": "Wisokyburgh",
      "zipcode": "90566-7771",
      "geo": {
        "lat": "-43.9509",
        "lng": "-34.4618"
      }
    },
    "phone": "010-692-6593 x09125",
    "website": "anastasia.net",
    "company": {
      "name": "Deckow-Crist",
      "catchPhrase": "Proactive didactic contingency",
      "bs": "synergize scalable supply-chains"
    }
  },
......
  }
]

このクラスが正常に機能しているかテストをするためには、下記の項目をクリアしないといけませんよね。

  • APIを叩いた時にstatus code 200が返ってきている
  • 返ってきた値が期待する値と同じ

レスポンスが200かどうか確認するために毎回APIを叩くことは、外部APIに対して負荷を多くかけてしまうことや、テストが肥大化していくと通信時間が長くなってしまい全体のテスト時間が長くなってしまうといったことがあるので色々と不便。

ひとまず200が返ってくる前提で、返ってきた値が期待している値と同じかどうかを試したい。

そんな時に、webmockが使えます。

実装

#spec/jsonplaceholder_spec.rb

require 'rails_helper'
require 'webmock/rspec'

RSpec.describe Jsonplaceholder do
  describe "#access" do
    context 'when success access' do
      before do
        jsonplaceholder_request('users')
      end

      it do
        expect{ subject }.not_to raise_error 
      end
    end
  end

  def jsonplaceholder_request(endpoint)
    WebMock.stub_request(:get, "https://jsonplaceholder.typicode.com/#{endpoint}")
    .to_return(
      body: File.read("#{Rails.root}/test/fixtures/jsonplaceholder/response.json"),
      status: 'OK',
      headers: { 'Content-Type' => 'application/json'}
    )
  end
end

webmockを使用するために、上部で読み込んでいます。この記述のおかげで、stub_requestを使えるようになります。

require 'webmock/rspec'
stub_request(:any, "www.example.com")

Net::HTTP.get("www.example.com", "/")    # ===> Success

subjectを使っています。これは、describeもしくはcontextの””で囲まれた部分(今回だと#access)にリクエストを飛ばしてくれるrspecのDSLです。subjectと書くだけで、Jsonplaceholder#accessに対してリクエストを送ってくれます。

  describe "#access" do
    context 'when success access' do
      before do
        jsonplaceholder_request('users')
      end

      it do
        expect{ subject }.to have_http_status(:ok)
      end
    end
  end

同一ファイルないで複数のテストケースを書く場合は、「このsubjectってどこに対してリクエストしてるんだ?」とページをスクロールして確認しないといけないので、あまり推奨はされていないみたいです。

僕がRSpecでsubjectを使わない理由 - give IT a try

implicitly defined subject

stub_requestは、webmockをrequireしたおかげで使用できるようになった記述です。stub = double(hoge) allow(stub).to receive(:huga).and_return(1)というような記述に似ていますね。

  def jsonplaceholder_request(endpoint)
    WebMock.stub_request(:get, "https://jsonplaceholder.typicode.com/#{endpoint}")
    .to_return(
      body: File.read("#{Rails.root}/test/fixtures/jsonplaceholder/response.json"),
      status: 'OK',
      headers: { 'Content-Type' => 'application/json'}
    )
  end

後述

jsonplaceholderを外部APIとして使ったのであまり利点がわかりずらくなってしまいました。

SalesforceとRailsを連携させたプロジェクトなど、外部APIの仕様が複雑な場合は便利だと思います。

https://github.com/restforce/restforce

他にも、vcrというモック機能を提供できるgemがあるようです。

webmockだと、別途データ作成や管理が必要でしたが、vcrがあると全てよしなにやってくれるようです。

VCRを使うとRSpecのWebmockの作成が超絶楽になった!

ブラックボックステストに便利なツールを積極的に使用して、健やかなRspecライフを。

参考文献

https://qiita.com/seimiyajun/items/3a131e1afba8b2ae46a6
VCRを使うとRSpecのWebmockの作成が超絶楽になった!
https://github.com/restforce/restforce
僕がRSpecでsubjectを使わない理由 - give IT a try

implicitly defined subject
https://www.miracleave.co.jp/contents/1023/rspec_webmock_stub/

Discussion