Just do IT

思うは招く

webmock でAPIキーをダミーにして外部API通信するフリをしたい(Rails)

やりたいこと

  • webmock でAPIキーをダミーにして外部API通信するフリをしたい
  • Custom Search JSON API を使った箇所をRailsでテストをしたい

手順

Custom Search JSON API  |  Programmable Search Engine を使って開発をしていたときのこと。

Google公式が用意しているAPI利用gem googleapis/google-api-ruby-client: REST client for Google APIs を使い、検索結果を取得するクラスを作成した。

たとえば、こんな感じ。

class GoogleSearch
  def initialize(query:, url:, api_key:, cse_id:)
    @query = query
    @url = url
    @api_key = api_key
    @cse_id = cse_id
  end
~

初期化時にAPIキーを入れて使うような設計になっている。

今回、APIへのリクエストにたいするレスポンスをwebmockでスタブ登録して使おうとした時、すこし手こずった。

テストはminitestを使用しているが、RSpecでも根幹の部分は変わらないと思う。

テストファイルはこんな感じ。

test/models/google_search_test.rb

# frozen_string_literal: true

require 'test_helper'

class AmazonTest < ActiveSupport::TestCase
  setup do
    stub_google!
    @google = GoogleSearch.new(
      query: 'ハンターハンター',
      url: "https://ja.wikipedia.org/wiki/HUNTER%C3%97HUNTER",
      api_key: 'mock_api_key',
      cse_id: 'mock_cse_id'
    )
  end

  #リクエスト時に送ったURLが検索順位1位であることのテスト
  test "#fetch_ranking" do
    assert_equal 1, @google.fetch_ranking
  end
end
  • ハンターハンター」と検索している”てい”
  • https://ja.wikipedia.org/wiki/HUNTER%C3%97HUNTERというURLが何位なのかチェックしてほしい”てい”
  • mock_api_keyAPIキーをいれてる”てい”
  • mock_cse_id検索エンジンIDを入れている”てい”

これらはすべて「てい」なので、Web API にリクエストを送るフリをしているだけ。本当は Webmock がレスポンスを返してくれる。

このテストファイル内でスタブ登録もできるが、実際に開発するなら別ファイルに切り出すと思う。今回もそのようにした。

test/supports/stub_helper.rb

module StubHelper
  def stub_google!
    json = File.read("#{Rails.root}/test/fixtures/search_results.json")
    stub_request(:get, "https://customsearch.googleapis.com/customsearch/v1?cx=mock_cse_id&key=mock_api_key&num=100&q=ハンターハンター").to_return(status: 200, body: json, headers: { "Content-Type" =>  "application/json" })
  end
end

ここでのポイントは、"https://customsearch.googleapis.com/customsearch/v1?cx=mock_cse_id&key=mock_api_key&num=100&q=ハンターハンター"と設定している点。

これはテストファイルでの

@google = GoogleSearch.new(
  query: 'ハンターハンター',
  url: "https://ja.wikipedia.org/wiki/HUNTER%C3%97HUNTER",
  api_key: 'mock_api_key',
  cse_id: 'mock_cse_id'
)

に合わせている。

これでテストは通過した。

$ rails test test/models/google_search_test.rb 
Running via Spring preloader in process 61289
Run options: --seed 41283

# Running:

.

Finished in 0.110767s, 9.0280 runs/s, 9.0280 assertions/s.
1 runs, 1 assertions, 0 failures, 0 errors, 0 skips

学び

当初は、以下のようにスタブを登録していたがエラーが出ていた。

#ダメだった方法
stub_request(:post, "https://customsearch.googleapis.com/customsearch/v1").~
  • postになっている
  • URLに検索キーワードやAPIキー、検索エンジンIDが入っていない

これでエラーが出て、ずっと「なんでやね〜ん」となっていた。

今考えると、ここでのリクエストとは、検索エンジンで検索する行為と同じことだ。そりゃこのURLでは何も返ってこないわけだ。POSTリクエストではなく、GETリクエストにするのも確かにそうだ。

Webmockのエラーメッセージには「GETで"https://customsearch.googleapis.com/customsearch/v1?cx=mock_cse_id&key=mock_api_key&num=100&q=ハンターハンター"ってな感じでリクエストしたらOKやで〜」と出ていた。やっぱりエラーメッセージをよく読むことの重要性を身をもって痛感した😅

Webmockでスタブ登録するうえでは、HTTPメソッドとリクエスURIの設定が重要なんだとわかった。

参考ファイル

gemfile

group :development, :test do
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
  gem 'webmock'
end

test/test_helper.rb

ENV['RAILS_ENV'] ||= 'test'
require_relative '../config/environment'
require 'rails/test_help'
require 'webmock/minitest' #minitest用のwebmockを使用
require "supports/stub_helper" #スタブヘルパーをrequire

# WebMock.allow_net_connect!

class ActiveSupport::TestCase
  # Run tests in parallel with specified workers
  parallelize(workers: :number_of_processors)

  # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
  fixtures :all

  # Add more helper methods to be used by all tests here...
  include StubHelper #ここで使えるようにしている
end