Grape は Ruby で API を書くのに便利なフレームワークです。
Grape 自体については fakestarbaby 氏がすでにすてきなエントリを書いてくださっています。
Grape | API生成マイクロフレームワーク #Rails #Gems #Ruby #grape #api_builders - Qiita
ここではどうやってテストを書くのかということについて書いてみたいと思います。
想定
- RSpec の受け入れテストの request_spec を使うよ
- API は JSON を返すよ
- API 用のサブドメイン(api.foobar.com)を切っているよ
- JSON のテストは json_expressions を使うよ(参考)
- OAuth 2.0 の Provider になって Web Application Flow とかで認証しちゃったり
- モックは FactoryGirl で作っちゃうよ
helper
まずは helper メソッドをいくつか api_helper.rb に定義します。
include Rack::Test::Methods
Rack::Test::Methods をインクルードすることで、get, post 等々を事項した結果のテストをおこなえるようになります。
module ApiHelper
include Rack::Test::Methods
end
API 用のサブドメインのための設定
API 用のサブドメインを切ったのでそれに応じるように、ヘルパーメソッドを定義しておきます。
テストの場合、ドメイン名は example.com になるようなので、サブドメインは api.example.com にしています。さきほど api.foobar.com と書いたのはこのときの api.example.com と混同しないようにするためです。
module ApiHelper
# (略)
def domain
'api.example.com'
end
def default_rack_env
{ 'HTTP_HOST' => domain }
end
end
shared_context で全体の前提を定義!
module ApiHelper
# (略)
shared_context 'api' do
subject { eval("#{method}(url, parameters, rack_env)") }
let(:parameters) { '' }
let(:rack_env) { default_rack_env }
end
end
テスト用の request は HTTPメソッド名(url, parameters, rack_env) のようになるのですが、毎回書くのはかったるいので、subject に eval で押し込めました。
RSpec の subject を使うことで、it do end の中身は subject が実行された結果を受けるので、いきなり should から書き始めることができます。
HTTP の status と body を一気に試す shared_examples_for を定義!
modulde ApiHelper
# (略)
shared_examples_for '200 Success' do
its(:status) { should be(200) }
its(:body) { should match_json_expression(result) }
end
shared_examples_for '201 Created' do
its(:status) { should be(201) }
its(:body) { should match_json_expression(result) }
end
shared_examples_for '204 No Content' do
its(:status) { should be(204) }
its(:body) { should eq('') }
end
shared_examples_for '404 Not Found' do
its(:status) { should be(404) }
its(:body) { should match_json_expression({ message: '404 Not Found' }) }
end
shared_examples_for '422 Unprocessable Entity' do
its(:status) { should be(422) }
its(:body) { should match_json_expression({ message: '422 Unprocessable Entity' }) }
end
end
先ほどの subject の結果で得られる last_response は status / body などのプロパティを持っているので、RSpec の its でさらに取り出してしまいます。
20x などの成功時の match_json_expression の中身は、使用時に let を使い result という名前で定義します。
OAuth 2 認証
OAuth 2 の認証が必要な場合は、先ほどの shared_context を拡張します。
module ApiHelper
# (略)
def oauth2_auth(token)
header('Authorization', "Bearer #{token}")
end
shared_context 'api' do
let(:user) { FactoryGirl.create(:user) }
let(:client_application) { FactoryGirl.create(:client_application,
user: user) }
let(:access_token) { FactoryGirl.create(:access_token,
user: user,
client_application: client_application) }
before(:each) { oauth2_auth(access_token.token) }
subject { eval("#{method}(url, parameters, rack_env)") }
let(:parameters) { '' }
let(:rack_env) { default_rack_env }
end
end
実際の使い方
ここまで作った helper を使って、実際にテストを書いてみましょう。
describe Api do
include ApiHelper
include_context 'api'
describe 'Articles' do
let(:article) { FactoryGirl.create(:article, user: user) }
describe 'GET /articles/:article_id' do
let(:method) { 'get' }
let(:url) { "/articles/#{article.id}" }
let(:result) do
{
id: article.id,
title: article.title,
content: article.content,
user: {
id: user.id,
name: user.name,
profile_image: user.profile_image.url(:small),
created_at: user.created_at.as_json,
updated_at: user.created_at.as_json,
}
}
end
it_behaves_like('200 Success')
end
describe 'DELETE /articles/:article_id' do
let(:method) { 'delete' }
let(:url) { "/articles/#{article.id}" }
it_behaves_like('204 No Content')
it 'The article is successfully deleted.' do
subject
expect { Article.find(article.id) }.to raise_error ActiveRecord::RecordNotFound
end
end
end
end
リクエスト時の parameter については必要があれば let は上書きができるので、上書きします。method や url は上の方で定義しておいて、context をネストさせれば、無駄なコードを書くことなくテストのコードの集中することが可能だと思います。
subject を it do の中で呼び出せば、response でない項目もテストが可能です。