概要
ONEのAPIはリリース当初からRuby on Railsで開発しており、少人数でメンテナンスを続けてきました。RSpecでテストを書くことは初期から続けており、現在カバレッジは87%です。RubyやRSpecの表現力を損なうことなく、シンプルにテストを書いていけいるようなTipsを紹介していきたいと思います。
- Ruby 2.6.6
- Ruby on Rails 6.0.4
- RSpec 3.10.0
その他Railsで行っていること
- Sidekiq
- DBをmasterとreplicaに分割
Modelのテスト
簡単なModelのテストを使って説明していきたいと思います。 image_url: true
の部分は独自のValidatorを作っています。
# == Schema Information
#
# Table name: brands
#
# id :uuid not null, primary key
# logo_url :string
# created_at :datetime not null
# updated_at :datetime not null
#
class Brand < ApplicationRecord
validates :logo_url, image_url: true, allow_nil: true
end
テストコードです。describeでattributeを指定し、それぞれのcontextでattributeの値を変更しています。
RSpec.describe Brand, type: :model do
describe '#logo_url' do
subject { brand.validate(:logo_url) }
let(:brand) { build(:brand) }
before do
brand.logo_url = logo_url
end
context 'when logo_url exists' do
let(:logo_url) { 'https://assets.wow.one/img/logo.jpg' }
it { is_expected.to be_valid }
end
context 'when logo_url is nil' do
let(:logo_url) { nil }
it { is_expected.to be_valid }
end
context 'when logo_url is not image url' do
let(:logo_url) { 'https://example.com' }
it { is_expected.to be_invalid }
end
end
end
この部分がとてもシンプルになっているのがわかると思います。細かくどんなエラーになるかをテストするには不十分ですが、これで十分としています。
it { is_expected.to be_valid }
どう実現しているかと言うと、 subject { brand.validate(:logo_url) }
で利用している validate
メソッドを定義しています。
module RSpec
class ActiveRecord::Base
class Validation
attr_accessor :errors
def initialize(errors)
@errors = errors
end
def valid?
@errors.empty?
end
def invalid?
!valid?
end
end
def validate(key)
valid?
Validation.new(errors[key])
end
end
end
Requestのテスト
APIのschemaはOpenAPIを利用しています。途中から導入したので、全てのAPIが網羅されている状態ではありませんが、徐々にschemaと実装が合うことを保証していっています。interagent/committee と willnet/committee-rails を利用して、テストコードを書いています。
subject { post '/admin/color_themes', params: params }
context 'when the user is an operator', operation: true do # operator権限のstubをする
it 'creates a color_theme' do
expect { subject }.to change(ColorTheme, :count).by 1
assert_response_schema_confirm 201 # commiteeでresponseの確認をする
end
context 'when the parameter is wrong' do
let(:params) { {} }
# よくあるエラーのレスポンスはshared_examplesを定義
it_behaves_like 'follows openapi', status: 400
end
end
エラー用のshared_examplesです。
RSpec.shared_examples 'follows openapi' do |status:, code: nil, message: nil|
title = "follows openapi, status is #{status}"
title += ", and error code is #{code}" if code
title += ", and error message is #{message}" if message
it title do
subject
expect(response).to have_http_status(status)
expect(json['code']).to eq code if code
expect(json['message']).to eq message if message
assert_response_schema_confirm(status)
end
end
開発中、 committee gem がボディーが空の304 (Not Modified)応答をバリデートしようとしてこけてしまう問題に当たったため、PRを出してマージしてもらいました。