テストコードは本当に理解できず一周目は正直飛ばしてましたが
復習してなんとなく理解できました!
というわけで復習の備忘録です!
目的
- Userモデルの単体テストの実装を通して、正常系テストを理解
- Userモデルの単体テストの問題を解いて、異常系テストに関する知識を定着
- Tweetモデルの単体テストの問題を解いて、モデル単体テストに関する知識を定着
####文字数制限のバリデーションを設置
「保存できる値は最大6文字まで」というバリデーションを設置
app/models/user.rb
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :tweets
has_many :comments # commentsテーブルとのアソシエーション
validates :nickname, presence: true, length: { maximum: 6 }
end
フォームに設けている文字数制限を削除
app/views/devise/registrations/new.html.erb
<div class="contents row">
<div class="container">
<h2>Sign up</h2>
<%= form_with model: @user, url: user_registration_path, id: 'new_user', class: 'new_user', local: true do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>
<div class="field">
<%= f.label :nickname %> <em>(6 characters maximum)</em><br />
<%= f.text_field :nickname, autofocus: true %>
</div>
<%# 以下省略 %>
Userのモデル単体テストの実装
exampleを整理
モデル単体テストはバリデーションやメソッドの検証です
バリデーションは「属性の値がDBに保存されても良いかチェックを行う条件」を設定
presence
は、指定した属性の値の有無をチェック
Userモデルでは、nickname
に対して、presence:true
の条件を指定しています
この場合、DBに保存する前にnicknameに値が存在していなければ、条件に引っ掛かりDBに保存されません
length
は、指定した属性の値の文字数制限をチェックします
Userモデルでは、nickname
に対して、length: { maximum: 6 }
の条件を指定しています。
- nicknameとemail、passwordとpassword_confirmationが存在すれば登録できる
- nicknameが空では登録できない
- emailが空では登録できない
- passwordが空では登録できない
- passwordとpassword_confirmationが不一致では登録できない
- nicknameが7文字以上では登録できない
- 重複したemailが存在する場合は登録できない
- emailは@を含まないと登録できない
- passwordが5文字以下では登録できない
- passwordが129文字以上では登録できない
新規登録できる場合のテストコードを記述
- 検証したいモデルのインスタンスを生成
- 生成したインスタンスがどのような状況であればいいか記述
be_valid
valid?
のメソッドの返り値が、true
であることを期待するマッチャ
% rails c
[1] pry(main)> @user = FactoryBot.build(:user)
(4.3ms) SET NAMES utf8, @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'), @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
=> #<User id: nil, email: "judie@gmail.com", created_at: nil, updated_at: nil, nickname: "GP">
[2] pry(main)> @user.valid?
User Exists? (10.3ms) SELECT 1 AS one FROM `users` WHERE `users`.`email` = BINARY 'judie@gmail.com' LIMIT 1
=> true
spec/models/user_spec.rb
it 'nicknameとemail、passwordとpassword_confirmationが存在すれば登録できる' do
expect(@user).to be_valid
end
テストコード
bundle exec rspec spec/models/user_spec.rb
新規登録できない場合のテストコードを記述
spec/models/user_spec.rb
it 'nicknameが空で登録できない' do
@user.nickname = ''
@user.valid?
expect(@user.errors.full_messages).to include("Nickname can't be blank")
end
it 'emailが空で登録できない' do
@user.email = ''
@user.valid?
expect(@user.errors.full_messages).to include("Email can't be blank")
end
it 'passwordが空では登録できない' do
@user.password = ''
@user.valid?
expect(@user.errors.full_messages).to include("Password can't be blank")
end
it 'passwordとpassword_confirmationが不一致では登録できない' do
@user.password = '123456'
@user.password_confirmation = '1234567'
@user.valid?
expect(@user.errors.full_messages).to include("Password confirmation doesn't match Password")
end
it 'nicknameが7文字以上では登録できない' do
@user.nickname = 'aaaaaaa'
@user.valid?
expect(@user.errors.full_messages).to include('Nickname is too long (maximum is 6 characters)')
end
it '重複したemailが存在する場合は登録できない' do
@user.save
another_user = FactoryBot.build(:user)
another_user.email = @user.email
another_user.valid?
expect(another_user.errors.full_messages).to include('Email has already been taken')
end
it 'emailは@を含まないと登録できない' do
@user.email = 'testmail'
@user.valid?
expect(@user.errors.full_messages).to include('Email is invalid')
end
it 'passwordが5文字以下では登録できない' do
@user.password = '00000'
@user.password_confirmation = '00000'
@user.valid?
expect(@user.errors.full_messages).to include('Password is too short (minimum is 6 characters)')
end
it 'passwordが129文字以上では登録できない' do
@user.password = Faker::Internet.password(min_length: 129, max_length: 150)
@user.password_confirmation = @user.password
@user.valid?
expect(@user.errors.full_messages).to include("Password is too long (maximum is 128 characters)")
end
end
end
context
特定の条件を指定してグループを分けます
使用方法はdescribe
と同じですが、describe
には何についてのテストなのかを指定するのに対し、cotext
には特定の条件を指定
require 'rails_helper'
RSpec.describe User, type: :model do
before do
@user = FactoryBot.build(:user)
end
describe 'ユーザー新規登録' do
context '新規登録できるとき' do
it 'nicknameとemail、passwordとpassword_confirmationが存在すれば登録できる' do
expect(@user).to be_valid
end
end
context '新規登録できないとき' do
it 'nicknameが空で登録できない' do
@user.nickname = ''
@user.valid?
expect(@user.errors.full_messages).to include("Nickname can't be blank")
end
it 'emailが空で登録できない' do
@user.email = ''
@user.valid?
expect(@user.errors.full_messages).to include("Email can't be blank")
end
it 'passwordが空では登録できない' do
@user.password = ''
@user.valid?
expect(@user.errors.full_messages).to include("Password can't be blank")
end
it 'passwordとpassword_confirmationが不一致では登録できない' do
@user.password = '123456'
@user.password_confirmation = '1234567'
@user.valid?
expect(@user.errors.full_messages).to include("Password confirmation doesn't match Password")
end
it 'nicknameが7文字以上では登録できない' do
@user.nickname = 'aaaaaaa'
@user.valid?
expect(@user.errors.full_messages).to include('Nickname is too long (maximum is 6 characters)')
end
it '重複したemailが存在する場合は登録できない' do
@user.save
another_user = FactoryBot.build(:user)
another_user.email = @user.email
another_user.valid?
expect(another_user.errors.full_messages).to include('Email has already been taken')
end
it 'emailは@を含まないと登録できない' do
@user.email = 'testmail'
@user.valid?
expect(@user.errors.full_messages).to include('Email is invalid')
end
it 'passwordが5文字以下では登録できない' do
@user.password = '00000'
@user.password_confirmation = '00000'
@user.valid?
expect(@user.errors.full_messages).to include('Password is too short (minimum is 6 characters)')
end
it 'passwordが129文字以上では登録できない' do
@user.password = Faker::Internet.password(min_length: 129, max_length: 150)
@user.password_confirmation = @user.password
@user.valid?
expect(@user.errors.full_messages).to include("Password is too long (maximum is 128 characters)")
end
end
end
Tweetモデル単体テストコードの記述
テストコードを記述するファイルを作成
rails g rspec:model tweet
FactoryBotを準備
spec/factories/tweets.rb
FactoryBot.define do
factory :tweet do
text {Faker::Lorem.sentence}
image {Faker::Lorem.sentence}
association :user
end
end
association :user
は、user.rb
のFactoryBotとアソシエーションがあることを意味してる
Tweetのインスタンスが生成したと同時にm関連するUserのインスタンスも生成されます
Tweetに対しては、必ずUserが紐づいている必要があるため、このように記述する必要がある
(UserはTweetを必ず持っているわけではないため、User.rbには記述しません)
exampleの整理
- 画像とテキストを投稿できる
- テキストのみで投稿できる
- テキストが空では投稿できない
- ユーザーが紐付いていなければ投稿できない
require 'rails_helper'
RSpec.describe Tweet, type: :model do
before do
@tweet = FactoryBot.build(:tweet)
end
describe 'ツイートの保存' do
context 'ツイートが投稿できる場合' do
it '画像とテキストを投稿できる' do
expect(@tweet).to be_valid
end
it 'テキストのみ投稿できる' do
@tweet.image = ''
expect(@tweet).to be_valid
end
end
context 'ツイートが投稿できない場合' do
it 'テキストが空では投稿できない' do
@tweet.text = ''
@tweet.valid?
expect(@tweet.errors.full_messages).to include("Text can't be blank")
end
it 'ユーザーが紐づいてなければ投稿できない' do
@tweet.user = nil
@tweet.valid?
expect(@tweet.errors.full_messages).to include('User must exist')
end
end
end
end