今回はRspecについてまとめようと思います。
RSpecは、Rubyで書かれたアプリケーションの挙動・機能をテストするために利用されます。(テストフレームワーク)
導入
まずはRspecを使えるようにします。
group :test do
gem "rspec-rails"
end
Gemfileにrspec-railsというGemを追加して、bundle installを行います。
Gemをインストールしたら、rails generate rspec:installコマンドでセットアップを行います。
成功すれば、specフォルダが作成されているはずなので、確認しましょう。
rails generate rspec:installを実行する前に、config/application.rbに下記設定を追加することで、specフォルダ内にデフォルトで作成されるファイルを作成されないように設定できます。
config.generators do |g|
g.test_framework :rspec,
fixtures: false, # テストデータを作るfixtureを作成しない
view_specs: false, # テストデータを作るfixtureを作成しない
helper_specs: false, # ヘルパー用のスペックを作成しない
routing_specs: false # ルーティングのスペックを作成しない
end
これで、RSpecを実行する準備が整いました。
テストの配置ルールと命名規則
テストを実行するためにはファイルの配置ルールと命名規則に従う必要があります。
Ruby on Railsの場合は、次のように配置します。
spec/
models/
モデルファイル名_spec.rb
controllers/
コントローラファイル名_spec.rb
views/
erbファイル名_spec.rb
このように、Ruby on Railsのディレクトリ構成と同じ階層構成でspecディレクトリ配下に配置します。
Rspecに限らず、基本的なテストはデータを準備→確認したい処理を呼び出す→結果が想定通りか確認という流れになると思います。
その流れの中で、Rspecの書き方を見ていきます。
まずは、ファイルの先頭にはrequire 'rails_helper'をつけてください!
describe
まず、describeでテストの対象が何かを記述します。
describe "計算を確認するテスト" do
end
このように日本語でも書くことができます(もちろん、英語の可能です)
また、どのクラスのテストケースかを指定するだけでなく、 type: :modelや type: :controllerを指定することで、どのモジュールのテストかを指定できます。
controllerのテストで type: :requestを指定できます。type: :requestはURLの接続テストになるので、APIのテストという意味合いをもちます。(エンドポイントやPOST/GETなどのメソッドの種類を指定して実行します)
RSpec.describe Bookmark, type: :model do
end
context
contextは特定の条件や実施する際の条件を記載します。
describe "計算を確認するテスト" do
context "マイナスの2つの数字の足し算" do
calculator = Calculator.new
result = calculator.add(-1, -2)
end
end
contextはネストすることができるので、1つのdescribeの中に複数のcontextを記載できます。
describe "計算を確認するテスト" do
context "2つの数字を加算" do
context "2つのプラスの数字を加算" do
end
context "2つのマイナスの数字を加算" do
end
end
context "2つの数字を減算" do
end
end
it
itはアウトプットの内容を記載します。
describe "計算を確認するテスト" do
context "マイナスの2つの数字の足し算" do
calculator = Calculator.new
result = calculator.add(-1, -2)
it "-3が出力される(マイナス通しの足し算で想定通りになる)" do
expect(result).to eq(-3)
end
end
end
expectメソッドの引数にテスト対象コード(オブジェクト)を渡します。
expect(...).to につづけてマッチャーを書きます。
今回はeqでresultと-3が一致するかを確認しています。
resultと-3が一致しない場合にはテストケースの実行で失敗します。
eq以外にtrueかfalseかをチェックするbe_validなどマッチャーはいろんな種類があるので、公式ドキュメントを確認するといいでしょう。(最後の参照にリンク貼ってます)
before
beforeは各テストケースの前に実行されるコードブロックです。
テストケースの前にデータの準備を行いたいパターンやいろんなテストケースで共通に行いたい処理を記載するなどができます。
describe "計算を確認するテスト" do
before do
# セットアップコード
end
it "マイナス通しの足し算" do
# テストケースの実装
end
it "0が含まれた足し算" do
# テストケースの実装
end
it "マイナスの数字とプラスの数字の足し算" do
# テストケースの実装
end
end
let
letは変数を定義する際に使用します。
describeかcontextの中でのみ使用可能です。
letとlet!の2種類あります。
letとlet!の違いは実行タイミングです。
letは利用時に実行され、let!は書かれた場所で実行されます。
letは遅延評価とも言われています。
{ create(:post,user: user) }はテストが実行されるまで実際には評価されません。
let(:post) { create(:post,user: user) }
let!にするとテストが始まる前に{ create(:post,user: user) }が実行され、
その結果がlet!の後の:postにセットされます。
let!(:post) { create(:post,user: user) }
つまり、letはあるテストケース内で変数を複数回使用するが、その値が変更されない時に便利です。
そして、let!はあるテストケース内で変数の値が変更される可能性があり、テストケースごとに初期化が必要な時に便利です。
テストの実行
specフォルダに移動して、bundle exec rspecコマンドで全テストケースを実行します。
bundle exec rspec ./spec/ファイル名 というコマンドで特定のファイルに絞ってテスト実行もできます。
データ作成
テストデータを作成するときにはRspec標準で使えるfixtureに代わり、テストデータの準備をサポートしてくれるFactorybotを使用します。
fixtureを使用することもありますが、今回はFactorybotについて記載します。
Gem追加
Gemfileにfactory_bot_railsというGemを追加して、bundle installを行います。
group :test do
gem "rspec-rails"
gem "factory_bot_rails"
end
ファイル作成
rails g factory_bot:model モデル名
上記コマンドでfactoryファイルを作成します。test/factoriesに作成されます。
モデル作成
ファイル作成で作られたファイルがUserだと仮定して、Userのテストデータを作成します。
FactoryBot.define do
factory :user do
sequence :email do |n|
"person#{n}@example.com"
end
password { "Password1" }
password_confirmation { "Password1" }
name { "test" }
status { 0 }
end
password { "Password1" }はpasswordが固定で"Password1"という値になっていることを表しています。
sequence :email do |n|
"person#{n}@example.com"
end
上記はnの部分が連番でデータが作成されます。
データを呼び出す
作成したテストデータを呼び出す場合には下記のようにcreate(:user)もしくは、build(:user)とすることで作成したデータを使用できます。
require "rails_helper"
describe "xxxxx" do
before do
@user = create(:user) #ここで定義
end
context "xxxxx" do
xxxxx
end
end
createとbuildには下記違いがあります。
create
- DB上にインスタンスを永続化する。
- DB上にデータを作成する。
- DBにアクセスする処理のときは必須。(何かの処理の後、DBの値が変更されたのを確認する際は必要)
build
- メモリ上にインスタンスを確保する。
- DB上にはデータがないので、DBにアクセスする必要があるテストのときは使えない。
- DBにアクセスする必要がないテストの時には、インスタンスを確保する時にDBにアクセスする必要がないので処理が比較的軽くなる。
さらに、データを上書きすることもできます。
@user = create(:user, name: "test更新")
このようにすることで、nameがtest更新のユーザーデータを作成できます。
trait
traitはパターンA、B 、C などデータをパターン分けして準備できる機能です。
FactoryBot.define do
factory :user do
sequence :email do |n|
"person#{n}@example.com"
end
password { "Password1" }
password_confirmation { "Password1" }
name { "test" }
status { 0 }
trait :fixed_id do
id { 100 }
end
end
上記は先ほどUserモデルにidが固定バージョンのデータを追加しました。
let(:user) { create(:user, :fixed_id) }のようにfixed_idを追加することで指定したデータにできます。
こうすることで、毎回idが固定は困るけど、特定のケースではidを固定で使用したいなど使い分けができます。
複数データを作成する
複数データを作成する場合に、個数分createするのは骨が折れる作業です。
そこで、FactoryBotのcreate_listメソッドを使って複数個のデータを一気に作成できます。
users = create_list(:user, 5)
または、Userの生成時に、Taskも一緒に生成したい場合には下記のようにすることができます。
trait :with_tasks do
after(:create) do |user|
create_list(:task, 5, user:)
end
end
TransientとEvaluatorを使用した関連データの生成
trasientはファクトリの生成時に動的なデータにする属性です。
transientをcallback関数内で参照したい場合、evaluatorを使います。
callback関数のブロック引数でevaluatorを宣言することで、transientの値を参照することができます。
FactoryBot.define do
factory :user do
end
trait :with_tasks do
transient do
tasks_count { 5 }
end
after(:create) do |user, evaluator|
# evaluatorを経由して、transientのtasks_countを参照している
create_list(:knowledge, evaluator.tasks_count, user:)
end
end
end
上記はUserのwith_tasksで、生成するtaskの数を指定したい場合の定義です。
画像のサンプルを用意
画像のサンプルを用意する場合にはspec/fixtures配下に画像を置いてください。
画像のアップロードを行うテストはfixture_file_uploadメソッドを使用します。
例えば、下記はアップロードされた画像とみなすことができます。
let(:image1) { fixture_file_upload("spec/fixtures/image.png", "image/png") }
おまけ
type: :requestでheaderを指定するのは簡単ですが、type: :controllerの場合には少し工夫が必要です。
describe 'Sample' do
let(:header) { { 'X-Requested-With': "XMLHttpRequest" } }
let(:params) { { 'TEST': "SAMPLE" } }
it 'set header' do
post 'path/to/endpoint', params: params, headers: header
expect(response.status).to eq 200
end
end
type: :controllerの場合にはheaderを指定するだけで適用されますが、type: :controllerでは指定できません。
require 'rails_helper'
RSpec.describe Api::LoginsController, type: :controller do
let(:header) { { 'X-Requested-With': "XMLHttpRequest" } }
describe 'logout' do
it 'returns status 204' do
request.headers.merge!(header)
post :logout
expect(response.status).to eq(204)
end
end
end
end
type: :controllerで使用する場合にはrequest.headersに追加したいヘッダー情報をマージしましょう。
参照
rspec.toolboxforweb.xyz
hackmd.io
zenn.dev
github.com
qiita.com
qiita.com
qiita.com