はじめに
OAuth 2.0 のトークンを使用して API にリクエストする機会が今までなかったのですが、先日、急に必要になったので困りました。ひとまずしのぎました。
そのついでに、RSpec で oauth2 のレスポンスをモックするにはどうすればよいのだろうと思ったので、試してみます。
X API v2 を題材にします。
oauth2 の gem を使用します。
gem "oauth2", "~> 2.0"
実際にリクエストを送信する
まず、実際にリクエストを送信して本物の結果を取得するコードを書いてみます。
RSpec.shared_examples 'Actually sending the request' do
describe '実際にリクエストを送信する処理が' do
let(:oauth2_client) do
options = { site: 'https://api.x.com' }
OAuth2::Client.new(ENV.fetch('CLIENT_ID'), ENV.fetch('CLIENT_SECRET'), options)
end
let(:access_token) do
OAuth2::AccessToken.from_hash(oauth2_client, { token: token_code })
end
it '成功すること' do
response = access_token.get('/2/users/me')
expect(response.parsed[:data][:id]).to eq '1111'
end
end
end
RSpec.describe 'XV2Oauth2Api' do
let(:token_code) { 'aaa' }
include_examples 'Actually sending the request'
end
OAuth2::AccessToken
のインスタンスがリクエストを送信し、レスポンスを生成しています。このインスタンスをモックに差し替えることができれば、任意のレスポンスを返却させることができそうです。
リクエストとレスポンスをモックする
OAuth2::AccessToken
クラスの from_hash
メソッドを上書きするコードを用意します。
RSpec.shared_context 'with oauth2 access token mock' do
let(:stub_access_token) do
allow(OAuth2::AccessToken).to receive(:from_hash).and_return(mock_access_token)
end
let(:mock_access_token) do
mock = instance_double('access_token')
allow(mock).to receive(:get).and_return(OAuth2::Response.new(http_response))
mock
end
let(:http_response) do
mock = instance_double('http_response')
allow(mock).to receive(:headers).and_return({ 'Content-Type' => 'application/json' })
allow(mock).to receive(:status).and_return(200)
allow(mock).to receive(:body).and_return(JSON.generate(response_data))
mock
end
let(:response_data) do
{
data: {
id: '1111',
name: 'ユーザー1',
username: 'user1'
}
}
end
end
OAuth2::AccessToken
の代わりのオブジェクトを生成し、自前で用意した OAuth2::Response
インスタンスが返却されるようにしました。
使用してみます。
RSpec.shared_examples 'Mocking an Access Token' do
describe 'アクセストークンのモックが' do
include_context 'with oauth2 access token mock'
before do
stub_access_token
end
let(:oauth2_client) do
options = { site: 'https://api.x.com' }
OAuth2::Client.new(ENV.fetch('CLIENT_ID'), ENV.fetch('CLIENT_SECRET'), options)
end
let(:access_token) do
OAuth2::AccessToken.from_hash(oauth2_client, { token: token_code })
end
it '成功すること' do
response = access_token.get('/2/users/me')
# puts response.inspect
expect(response.parsed[:data][:id]).to eq('1111')
end
end
end
RSpec.describe 'XV2Oauth2Api' do
let(:token_code) { 'aaa' }
include_examples 'Mocking an Access Token'
end
「実際にリクエストを送信する」のコードと同じように見えますが、リクエストは送信されず、自前で用意した結果が返却されます。
puts response.inspect
で返却値を確認すると、InstanceDouble オブジェクトが返却されていることがわかります。
アプリケーションコード内で送信されるリクエストをモックする
アプリケーションコードが以下のように書かれているとします。
class Oauth2Util
def self.make_client
options = { site: 'https://api.x.com' }
OAuth2::Client.new(ENV.fetch('CLIENT_ID'), ENV.fetch('CLIENT_SECRET'), options)
end
def self.make_access_token(token_code)
client = make_client
options = { token: token_code }
OAuth2::AccessToken.from_hash(client, options)
end
end
class XV2Oauth2Api
def initialize(access_token:)
@access_token = access_token
end
def get_me
@access_token.get('/2/users/me')
end
end
独自クラス XV2Oauth2Api
のインスタンスメソッドでリクエストが送信されています。その結果をモックできるかを確認します。
RSpec.shared_examples 'Mocking access tokens created in application code' do
describe 'アプリケーションコード内で作られているアクセストークンのモックが' do
include_context 'with oauth2 access token mock'
before do
stub_access_token
end
let(:x_v2_oauth2_api) do
access_token = Oauth2Util.make_access_token(token_code)
XV2Oauth2Api.new(access_token: access_token)
end
it '成功すること' do
response = x_v2_oauth2_api.get_me
# puts response.inspect
expect(response.parsed[:data][:id]).to eq('1111')
end
end
end
RSpec.describe 'XV2Oauth2Api' do
let(:token_code) { 'aaa' }
include_examples 'Mocking access tokens created in application code'
end
「リクエストとレスポンスをモックする」の shared_context をそのまま使用しています。before
で OAuth2::AccessToken
クラスの from_hash
メソッドが上書きされます。
こちらも、puts response.inspect
でレスポンスがモックされていることが確認できます。
おわりに
OAuth 2.0 のトークンを使用したリクエストと、そのレスポンスをモックすることができました。
通信処理があるとテストが難しくなりますが、モックする方法が確立できているとテストコードが書きやすくなります。
テストコード、がんばります。