はじめに
RSpecのテストコードを書いて、大体の書き方や考え方などわかってきたので、その備忘録としてまとめます。
前提
- Docker
- Rails API
- 実行コマンド:
docker compose exec back bash rspec
基本のテストコード
複雑な実装でなければ下記のようなテストコードでも要件を満たすと考えています。
model spec
モデルに定義したバリデーションや振る舞いが期待通りか確認します。
例えば、次のカラムだった時を考えます。
create_table "categories", force: :cascade do |t|
t.datetime "created_at", null: false
t.string "name", null: false
t.datetime "updated_at", null: false
end
主に、nameに対して、空でないこと、重複がないことをバリデーションとします。
class Category < ApplicationRecord
validates :name, presence: true, uniqueness: true
end
FactoryBotを使ってテストデータを用意します。
FactoryBot.define do
factory :category do
sequence(:name) { |n| "category_#{n}" }
end
end
sequenceを使うと重複したデータが作成されないようにできます。
そして下記のようにテストコードをまとめました。
require 'rails_helper'
RSpec.describe Category, type: :model do
describe 'validations' do
context 'when all attributes are valid' do
let(:category) { build(:category) }
it 'is valid' do
expect(category).to be_valid
end
end
describe 'presence validation' do
context 'when name is blank' do
let(:category) { build(:category, name: nil) }
it 'is invalid' do
expect(category).to be_invalid
end
end
end
describe 'uniqueness validation' do
context 'when name already exists' do
let!(:category_before) { create(:category, name: "重複保存不可") }
let(:category) { build(:category, name: "重複保存不可") }
it 'is invalid' do
expect(category).to be_invalid
end
end
end
end
end
バリデーションに合わせて、createとbuildを使い分けると良いと思いました。
- buildはDBへ保存しないため、presenceなどDBを必要としないバリデーションの確認に便利
- createはDBへ保存するため、uniquenessなどDBに保存されたデータとの比較が必要な場合に使う
requests spec
データを作成して、想定したステータスやjsonが返ってくるか判断します。
次のようなコントローラーで考えます。
class Api::V1::HealthController < ApplicationController
def index
render json: { status: "success" }, status: :ok
end
end
APIのレスポンスやステータス、データの取得・更新が期待通りか確認します。
require 'rails_helper'
RSpec.describe 'Api::V1::Health', type: :request do
describe 'GET /api/v1/health' do
it 'returns status 200' do
get '/api/v1/health'
expect(response).to have_http_status(:ok)
json = response.parsed_body
expect(json).to include(
"status"
)
end
end
end
認証周りのテストについて
認証にはAuth、セッション、JWTなどさまざまな方法があります。
通常に加えて、Google認証などあるため、しっかりテストを書こうとすると複雑になります。
例えば、次のようなJWT認証ロジックがあるとします。長くなるため省略しています。
module Authenticatable
extend ActiveSupport::Concern
included do
before_action :authenticatable!
attr_reader :current_user
end
def authenticatable!
token = request.authorization&.split(" ")&.last
...省略
認証処理そのものではなく、認証後のAPIの挙動を確認したかったため、stubを使って認証済み状態を再現しました。
module AuthenticationHelper
def stub_authentication(user)
allow_any_instance_of(ApplicationController)
.to receive(:authenticatable!) do |controller|
controller.instance_variable_set(:@current_user, user)
end
end
end
リクエストスペックを次のようにまとめました。
require 'rails_helper'
RSpec.describe 'Api::V1::Categories', type: :request do
describe 'GET /api/v1/categories' do
let!(:user) { create(:user) }
let!(:category) { create(:category) }
let!(:question) { create(:question, category: category) }
let!(:choice) { create(:choice, question: question) }
context 'when authenticated' do
let(:headers) { { CONTENT_TYPE: 'application/json', Authorization: 'Bearer fake_token' } }
before do
stub_authentication(user)
end
it 'returns all categories with status 200' do
get '/api/v1/categories', headers: headers
expect(response).to have_http_status(:ok)
json = response.parsed_body
expect(json.first).to include(
'id',
'name',
'questions'
)
expect(json.first['questions'].first).to include(
'id',
'content',
'choices'
)
end
end
end
end
認証で重要なところは下記です。
let!(:user) { create(:user) }
let(:headers) { { CONTENT_TYPE: 'application/json', Authorization: 'Bearer fake_token' } }
before do
stub_authentication(user)
end
it 'returns all categories with status 200' do
get '/api/v1/categories', headers: headers
...省略
- userを作成してstub_authentication(user)へ渡す
- Authorizationヘッダーを付与して認証済みリクエストとして送る
共通項:エラーが起きたらデバッグ
正しくデータが作られないとしたら、バリデーションがどうなっているか確認します。
想定通りにメソッドへデータが送られないとしたら、データの型や構成、リクエストが正しいか確認します。
binding.pryを活用して、想定したデータが返ってきているか、レスポンスのJSON構造がどうなっているかなどを確認します。
おわりに
テストコードを書くのはめんどくさいし、最初は見て意味がわからないです。
しかし、慣れてくるとテストコードを書くのもだんだん作業になってくると感じました。
テストコードは
- テーブル設計
- JSONの構造
- create / build の違い
- FactoryBot
- stub
- アプリケーションのロジック
などを理解していれば、AIでもある程度生成できます。むしろAIを使ったほうが効率よくテストコードを書けると考えています。
そう考えたとき、テストコードを書く技術よりも「何を検証したいのか」を考えることが大切だと感じました。
参考