【RSpec】WebMockを利用して外部APIを利用したロジックのテストをする

Ruby

【Ruby】faradayを利用した外部API連携クラスの作成手順の記事で、Faradayを利用した外部APIとの連携方法について紹介しました。

外部APIを利用したロジックのテストコードでは、実際のAPIを利用するのではなくHTTPリクエストをスタブ化するのが一般的です。
Railsではwebmockというgemを利用することでHTTPリクエストのスタブ化ができます。

今回はWebMockを利用して、外部APIと連携したロジックのテストコードを作成する方法について紹介します。

テストコードの対象となるクラス

今回はQiitaClientAPIというQiita APIと連携するクラスのテストコードを作成します。
クラスにはget_itemsという記事一覧をQiitaから取得するメソッドが実装されています。

具体的なソースコードは以下の通りです。

lib/qiita_api_client.rb

class QiitaApiClient
  class HTTPError < StandardError
    def initialize(message)
      super "connection failed: #{message}"
    end
  end

  class << self
    def connection
      Faraday::Connection.new('https://qiita.com') do |builder|
        builder.authorization :Bearer, "#{Rails.application.credentials.qiita[:token]}"
        builder.request  :url_encoded # リクエストパラメータを URL エンコードする
        builder.response :logger # レスポンスを標準出力する
        builder.adapter Faraday.default_adapter # アダプターの選択。デフォルトはNet::HTTP
        builder.response :json, :content_type => "application/json" # レスポンスボディをJSONパースする
      end
    end

    def get_items
      begin
        response = connection.get(
          '/api/v2/items'
        )
        response.body
      rescue Faraday::ConnectionFailed => e
        raise QiitaApiClient::HTTPError.new(e.message)
      end
    end
  end
end

下準備

テストコードを実行するための準備を行ます。

RSpecのセットアップ

今回はテスティングフレームワークにRSpecを利用します。
rspec-railsをGemfileに追加します。

group :development, :test do
  gem 'rspec-rails'
end

gemのインストールとRSpecのセットアップを行ます。

$ bundle install
$ rails generate rspec:install

webmockのインストール

webmockをGemfileに追加し、インストールします。

gem 'webmock'
$ bundle install

テストコードの実装

以下ではWebMockを利用したテストコードの記述方法について紹介します。

正常系のテスト

正常系のテストコードは以下の通りです。

spec/lib/qiita_api_client_spec.rb

require 'rails_helper'

describe 'QiitaApiClient' do
  before do
    WebMock.enable! # WebMockの有効化
    allow(Rails.application.credentials).to receive(:qiita).and_return({token: '123'})
  end
  describe '.get_items' do
    let(:response_body) { [{ "title": "test" }].to_json }
    before do
      WebMock.stub_request(:get, "https://qiita.com/api/v2/items").
        with(
          headers: {'Authorization' => "Bearer 123"}
        ).
        to_return(
          body: response_body,
          status: 200,
          headers: { 'Content-Type' => 'application/json' }
        )
    end
    it 'データが取得できること' do
      expect(QiitaApiClient.get_items).to eq(JSON.parse(response_body))
    end
  end
end

WebMock.enable!でWebMockを有効化し、WebMock.stub_requestの箇所でHTTPリクエストのスタブを作成しています。

WebMockを利用することで「どういったリクエストに対して、どういったレスポンスを返す」という挙動が記述できます。これにより、外部API経由でデータのやりとりを行う状況が擬似的に作成できます。

なおbeforeに記載されているallow(Rails.application.credentials)の箇所はアプリケーションコードに記載されているRails.application.credentials.qiita[:token]に対してダミー値をセットするためのものです。

異常系(例外発生)のテスト

アプリケーションコードでは外部APIと正常に通信できなかった場合、エラーメッセージを添えてQiitaApiClient::HTTPErrorという例外を発生させています。

例外が発生したことを確認する異常系のテストコードは以下の通りです。

spec/lib/qiita_api_client_spec.rb

require 'rails_helper'

describe 'QiitaApiClient' do

  before do
    WebMock.enable!
    allow(Rails.application.credentials).to receive(:qiita).and_return({token: '123'})
  end

  describe '.get_items' do
    let(:response_body) { { "message": "error_message", "type": "error_type" }.to_json }
    before do
      WebMock.stub_request(:get, "https://qiita.com/api/v2/items").
        to_raise(Faraday::ConnectionFailed.new("some error"))
    end
    it '例外が発生すること' do
      # 例外のテストはexpect()ではなくexpect{}なので注意
      expect{QiitaApiClient.get_items}.to raise_error(QiitaApiClient::HTTPError, "connection failed: some error")
    end
  end
end

WebMockのto_raiseで例外が発生するスタブを作成し、expectのraise_errorで例外が発生していることを確認しています。

最終的なテストコード

今回紹介した正常系と異常系をまとめた最終的なテストコードは以下のようになります。

spec/lib/qiita_api_client_spec.rb

require 'rails_helper'

describe 'QiitaApiClient' do
  let(:qiita_base_uri) { 'https://qiita.com/api/v2' }
  before do
    WebMock.enable!
    allow(Rails.application.credentials).to receive(:qiita).and_return({token: '123'})
  end

  describe '.get_items' do
    subject { QiitaApiClient.get_items }
    context '成功' do
      let(:response_body) { [{ "title": "test" }].to_json }
      before do
        WebMock.stub_request(:get, "#{qiita_base_uri}/items").
          with(
            headers: {'Authorization' => "Bearer 123"}
          ).
          to_return(
            body: response_body,
            status: 200,
            headers: { 'Content-Type' => 'application/json' }
          )
      end
      it 'データが取得できること' do
        expect(subject).to eq(JSON.parse(response_body))
      end
    end
    context '失敗' do
      before do
        WebMock.stub_request(:get, "#{qiita_base_uri}/items").
          to_raise(Faraday::ConnectionFailed.new("some error"))
      end
      it '例外が発生すること' do
        expect{subject}.to raise_error(QiitaApiClient::HTTPError, "connection failed: some error")
      end
    end
  end
end

まとめ

WebMockを利用して、外部APIと連携したロジックのテストコードを作成する方法について紹介します。

WebMockを利用したテストコードのフローは以下のようになります。

  1. Webmock.enable!でWebMockを有効化する
  2. WebMock.stub_requestでHTTPリクエストのスタブを作成する
  3. 外部APIを利用するメソッドを実行し、実行結果の検証を行う

Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!