#RSpecを使用し、バリデーションのテストとコントローラーのテストを行う
商品出品画面を作る①へ
モデル
validate :images_presence
validates :name, :text, :category_id, :condition_id, :deliverycost_id, :pref_id, :delivery_days_id, :boughtflg_id,:user_id, presence: true
validates :price, presence: true, numericality: { greater_than_or_equal_to: 300, less_than_or_equal_to: 9999999 }
has_many_attached :images
belongs_to :user
belongs_to :category
def images_presence
if images.attached?
# inputに保持されているimagesがあるかを確認
if images.length > 10
errors.add(:image, '10枚まで投稿できます')
end
else
errors.add(:image, '画像がありません')
end
end
コントローラー
def new
@item = Item.new
@category_parent = Category.where("ancestry is null")
end
def create
@item = Item.new(item_params)
if @item.save
redirect_to root_path
else
render :new
end
end
private
def item_params
params.require(:item).permit(:name, :text, :category_id, :condition_id, :deliverycost_id, :pref_id, :delivery_days_id, :price, images: []).merge(user_id: current_user.id, boughtflg_id:"1")
end
end
これらのテストを行う
#FactoryBotで必要なデータを用意する
###商品
FactoryBot.define do
factory :item do
name {"hoge"}
text {Faker::Lorem.sentence}
condition_id {1}
deliverycost_id {1}
pref_id {13}
delivery_days_id {1}
price {"9999"}
boughtflg_id {1}
# アソシエーション
user
category
# 1枚の画像をアップロードする
trait :image do
after(:build) do |item|
File.open("#{Rails.root}/spec/images/Unknown.jpeg") do |f|
item.images.attach(io: f, filename: "Unknown.jpeg", content_type: 'image/jpeg')
end
end
end
# 11枚の画像をアップロードする
trait :images do
after(:build) do |item|
11.times do
File.open("#{Rails.root}/spec/images/Unknown.jpeg") do |f|
item.images.attach(io: f, filename: "Unknown.jpeg", content_type: 'image/jpeg')
end
end
end
end
end
end
trait :image:テストで呼び出す事で1枚の画像をアップロードできる
trait :images:テストで呼び出す事で11枚の画像をアップロードできる
アップロードの方法はActiveStorageの方法に準ずる
specディレクトリ以下にimagesディレクトリを用意し、その中にテストで使う画像を用意する
###ユーザー
FactoryBot.define do
factory :user do
password = Faker::Internet.password(min_length: 7)
nickname {"まーたろー"}
lastname {"真"}
firstname {"太郎"}
lastname_kana {"ま"}
firstname_kana {"たろう"}
zipcode {"1111111"}
pref_id {"1"}
city {"青梅市"}
address {"1-2-3"}
buildingname {"マンション"}
birthyear_id {"20"}
birthmonth_id {"5"}
birthday_id {"23"}
email {Faker::Internet.free_email}
password {password}
password_confirmation {password}
end
end
###カテゴリー
FactoryBot.define do
factory :category do
name {"hoge"}
ancestry {"1/99"}
end
end
カテゴリーは内容が少ないのでcategoryで用意しなくてもよかったかも・・・
#モデルのテスト
モデルはいろんな人が資料をだしているので、簡潔に画像部分だけ
RSpec.describe Item, type: :model do
describe '#create' do
let(:user){create(:user)}
let(:category){create(:category)}
context '保存できない' do
it '画像がない'do
item = build(:item, user: user, category: category)
item.valid?
expect(item.errors[:image]).to include('画像がありません')
end
it '画像が多い'do
item = build(:item, :images, user: user, category: category)
item.valid?
expect(item.errors[:image]).to include('10枚まで投稿できます')
end
###ポイント
*let(:user){create(:user)}とlet(:category){create(:category)}*でこのテストで使うユーザーとカテゴリーの情報を作成(create)する
factory :itemのtraitで用意した画像を呼び出すかどうかを決める
画像がない👉呼び出さない
画像が多い👉11枚の画像をアップロードする方を呼び出す
呼び出す時はbuild()の第二引数に指定する
#テスト実行
bundle exec rspec ./spec/models/item_spec.rb
#コントローラーのテスト
require 'rails_helper'
RSpec.describe ItemsController, type: :controller do
let(:user) { create(:user) }
let(:category) { create(:category) }
# テストで使用するitemに紐つくuserとcategoryを先にcreateする
describe 'GET #new' do
context 'ログインしている' do
it "@itemという変数が正しく定義されている" do
login user
#テストするユーザ情報をログイン状態にする
get :new
#HTTPメソッド:get でnewアクションを動かす
expect(assigns(:item)).to be_a_new(Item)
end
it '出品ページが表示される' do
login user
get :new
expect(response).to render_template :new
end
end
context 'ログインしていない' do
it 'ログインページに遷移する' do
get :new
expect(response).to redirect_to(new_user_session_path)
end
end
end
newアクションのテスト
-
ログインしている➡︎@item = Item.newが期待通りか?
:itemはfactory :itemで用意したデータを呼び出している
Itemはコントローラーの『@item = Item.new』を動かした時の@item
つまり、テストでは
expect(自分が用意したitemのデータ).to be_a_new(newしたインスタンス変数)
であり、コントローラーで言うと
newしたインスタンス変数 = 自分が用意したitemのデータ
となる -
ログインしている➡︎正しいページが表示されるか?
割愛 -
ログイン指定ない➡︎ログインページに遷移するか?
割愛
describe 'POST #create' do
let(:image) { fixture_file_upload('spec/images/Unknown.jpeg','image/jpeg' )}
# 画像をアプロードする行動を再現する
let(:item) { { item: attributes_for(:item, user_id: user.id, category_id: category.id )}}
# 画像以外のitemの配列を作成
let(:item_image) { { item: attributes_for(:item, user_id: user.id, category_id: category.id ,images: [image])}}
# 画像を含めたのitemの配列を作成
context 'ログインしている' do
subject { post :create, params: item_image}
context '保存できる' do
it 'レコードの数が増える' do
login user
expect{subject}.to change(Item, :count).by(1)
end
it 'トップページにリダイレクトする' do
login user
subject
expect(response).to redirect_to(root_path)
end
end
context '保存できない' do
subject { post :create, params: item}
it 'レコードの数が変わらない' do
login user
expect{subject}.to_not change(Item, :count)
end
it '出品画面に戻る' do
login user
subject
expect(response).to render_template :new
end
end
end
context 'ログインしていない' do
it 'ログインページに遷移する' do
post :create, params: item
expect(response).to redirect_to(new_user_session_path)
end
end
end
createアクションのテスト
-
ログインしている➡︎保存できる➡︎レコードの数が増える
expect{subject}.to change(Item, :count).by(1)
subject➡︎直前でまとめたsubject { post :create, params: item_image}
post :create➡︎HTTPメソッド:post でcreateアクションを動かす
params: item_image➡︎**let(:item_image)**を呼び出す
change(Item, :count).by(1)➡︎Itemモデルの数が1増えるか? -
ログインしている➡︎保存できる➡︎トップページにリダイレクトする
expect(response).to redirect_to(root_path)
response➡︎createアクションを動かした後に表示される画面
redirect_to(root_path)➡︎root_path(トップページ)にリダイレクトするか? -
ログインしている➡︎保存できない➡︎レコードの数が変わらない
-
ログインしている➡︎保存できない➡︎出品画面に戻る
-
ログインしていない➡︎ログインページに遷移する
#テスト実行
bundle exec rspec ./spec/controllers/items_controller_spec.rb
#ポイント
- テストは実際にコントローラやモデルを動かしているのでbinding.pryを使って止めて確かめられる
- ActiveStorageを使用しているのでfactoryで画像を準備する際にはActiveStorageのやり方に準ずる必要がある。
- コントローラーテスト中の用意はRSpecのやり方でOK
- 画像の扱いが出来れば大体できる!
-
let(:item_image) { { item: attributes_for(:item, user_id: user.id, category_id: category.id ,images: [image])}}
の部分はbinding.pryを使って形をよく確かめる!!