セッティングから基礎知識まで
詳細
everyday Rspec
describe User do
# 姓、名、メール、パスワードがあれば有効な状態であること
it "is valid with a first name, last name, email, and password"
# 名がなければ無効な状態であること
it "is invalid without a first name"
# 姓がなければ無効な状態であること
it "is invalid without a last name"
# メールアドレスがなければ無効な状態であること
it "is invalid without an email address"
# 重複したメールアドレスなら無効な状態であること
it "is invalid with a duplicate email address"
# ユーザーのフルネームを文字列として返すこと
it "returns a user's full name as a string"
end
説明
①期待する結果をまとめて記述(describe)している。
このケースではUserモデル
がどんなモデルなのか、そしてどんな振る舞いをするのかということを説明している。
②example(itで始まる1行)一つにつき、結果を一つだけ期待している。
first_name,
last_name,
email
のバリデーションをそれぞれ分けてテストしている点に注意する。
こうすれば、example が失敗したときに問題が起きたバリデーションを特定できる。
原因調査のためにRSpecの出力結果を調べる必要はありません。
少なくともそこまで細かく調べずに済む。
③どのexample
も明示的である。技術的なことを言うと、it
のあとに続く説明用の文字列は必須ではない。
しかし、省略してしまうとスペックが読みにくくなるため記述する。
④各example
の説明は動詞で始まっている。should
ではない。
期待する結果を声に出して読んでみると、下記の通り。
User is invalid without a first name (名がなければユーザーは無効な状態である)
User is invalid without a last name (姓がなければユーザーは無効な状態である)
User returns a user’s full name as a string (ユーザーは文字列としてユーザーのフルネームを返す)
可読性は 非常に重要であり、RSpec のキーとなる機
モデルのexpectとis_expectedの違い
belongs_toをチェックしたい
carrierwaveのテストをしたい
FactoryBot.define do
factory :sample_file do
id: 1
file Rack::Test::UploadedFile.new(File.join(Rails.root, 'spec/fixtures/sample.pdf'))
end
end
しかし、↑のファイル指定はrubocopに怒られた、、
参考:https://github.com/rubocop-hq/rubocop/issues/3779
FactoryBot.define do
factory :sample_file do
id: 1
file Rack::Test::UploadedFile.new(Rails.root.join('spec', 'fixtures', 'sample.pdf'))
end
end
uniqunessのテストをしたい
この処理繰り返し使いたい、、
shared_contextを使うと良さげです。
https://magazine.rubyist.net/articles/0035/0035-RSpecInPractice.html
Rspecのshared_context基礎の基礎
require 'rails_helper'
describe Person do
let(:person) { Person.new(age) }
shared_context 'infancy' do
let(:age) { 19 }
end
shared_context 'adult' do
let(:age) { 20 }
end
describe '#auth_smoke?' do
context 'when age greater equals to 20' do
include_context 'adult'
subject { person.auth_smoke? }
it { is_expected.to be_truthy }
end
context 'when age less than 20' do
include_context 'infancy'
subject { person.auth_smoke? }
it { is_expected.to be_falsy }
end
end
end
let!とbeforeの違いって何?
結論:書いた順に評価される
let!が先であれば、beforeに先立つし、逆であれば、beforeが先に評価される。
明確な違いが無いのであろうか。。?
subjectとis_expectedをセットで使う
subject(:user) { User.new(email: "hoge@example.com", password: "password") }
https://qiita.com/mm36/items/453d71d48dd8f462344c
http://kei-p3.hatenablog.com/entry/2016/05/25/235124
特定のテストをスキップしたいとき
pendingを用いて、テストが失敗しないようにする。
ちなみに、xit
とするだけで、特定のexampleをスキップ可能なので覚えておく。
Rspecでenumを使いたいとき
enumで数値型カラムを作る際に、テストデータを作る時に注意することを参考に
buildとcreateの違いを教えて
build()メソッドはインスタンスをメモリ上にのみ記録する。
create()メソッドはテストデータベースにも保存して、データを永続化させる。
FactoryBotで出来ることって何があるの?
記事はFactoryGirlに関する内容ですが、学べることが多いです。
https://tech.grooves.com/entry/2015/04/28/173025
あとは公式ドキュメントを読んでみる
インスタンスメソッドをテストしたい
例えば、userの姓と名を連結するメソッドname
があるとする。
def name
[firstname, lastname].join(' ')
end
これをテストしてみる。
it "returns a user's full name as a string" do
user = User.new(
first_name: "John",
last_name: "Doe",
email: "johndoe@example.com",
)
expect(user.name).to eq "John Doe"
end
※Rspecで等値のExpectationを書く時は、 ==
ではなく、eq
を使う。
スコープをテストしたい
Noteモデルに、渡された文字列でメモ(note)を検索する機能を付与。
scope :search, ->(term) {
where("LOWER(message) LIKE ?", "%#{term.downcase}%")
}
require 'rails_helper'
Rspec.describe Note, type: :model do
before do
@user = User.create(
first_name: "John",
last_name: "Doe",
email: "johndoe@example.com",
password: "password",
)
@project = @user.projects.create(
name: "Test Project",
)
end
# ユーザー、プロジェクト、メッセージがあれば有効な状態であること
it "is valid with a user, project, and message" do
note = Note.new(
message: "This is a sample note.",
user: @user,
project: @project,
)
expect(note).to be_valid
end
# メッセージがなければ無効な状態であること
it "is invalid without a message" do
note = Note.new(message: nil)
note.valid?
expect(note.errors[:message]).to include("can't be blank")
end
# 文字列に一致するメッセージを検索する
describe "search message for a team" do
before do
@note1 = @project.notes.create(
message: "this is the first note.",
user: @user,
)
@note2 = @project.notes.create(
message: "this is the second note.",
user: @user,
)
@note3 = @project.notes.create(
message: "First, preheat the oven",
user: @user,
)
end
# 一致するデータが見つかったとき
context "when a match is found" do
# 検索文字列に一致するメモを返すこと
it "returns notes that match the search term" do
expect(Note.search("first")).to include(@note1, @note3)
end
end
# 一致するデータが1件も見つからないとき
context "when no match is found" do
# 空のコレクションを返すこと
it "returns an empty collection" do
expect(Note.search("message")).to be_empty
end
end
end
end
FactoryBotで同時に複数個のインスタンスを生成したい
生成系のメソッドのそれぞれに対応する*_list()
メソッドで、同じデータを複数件生成できる。
build_list(), build_stubbed_list(), create_list(), attributes_for_list()
users = build_list(:user, 5, name: )