はじめに
ショッピングサイトアプリ(疑似フリマアプリ)を作成していて、購入機能を担当することになりました。
「RSpecでmodelのテストコードを作成せよ」とのお達しがありましたので、取り組んでみました。
その結果、associationで「belongs_to」の関係にあるモデルの連携に
ハマりましたので、備忘録もかねて投稿します。
前提
ここでは支払処理が完了した後に購入記録を登録する機能のテストを説明しています。
支払機能(payjplなどのAPI)について触れませんのでご了承ください。
参考記事
開発環境
ruby 2.5.1
rails 5.2.4.1
RSpec 3.9.0
factory_bot 5.1.1
devise 4.7.1
ER図
ショッピングサイトアプリのテーブル構成を次のように定義し、
rails db:migrateコマンドじ実行とmodelの構築が済んだものとします。
一見するとわかりにくいかもしれませんが、
このような考え方になります。
-
出品者
- ユーザーを登録する。(user)
- 発送元住所を登録する。(address)
- 売りたいモノを出品する。 (item)
-
購入者
- ユーザーを登録する。(user)
- 発送先住所を登録する。(address)
- 出品されたモノを購入する。 (trade)
ここで注意しなければならないことは
- userとaddressは出品者と購入者それぞれに必要であること。
- itemは出品者のみでがuserとaddressに従属すること。
- tredeは出品者のitemと、購入者のuserとaddressが必要であること。
となります。
他の開発者が作成したfactoryデータコードを流用しようとしましたが、出品者と購入者の2種類を同時に
テストするように想定されていないため、使い回しができませんでした(横着をしてはいけない)。
よって、購入機能専用に出品者と購入者のコードを書くことにしました。
Factorybotでテストデータを作ろう
まずはusers
FactoryBot.define do
# 出品者用データ factory名は「seller」とします。
# 「sequence」はspec.rbから呼び出されるたびにカウントアップしてくれる機能です。
factory :seller, class: User do
sequence(:nickname) { |i| "出品者_#{i}"}
sequence(:email) { |i| "seller_#{i}@test.com"}
password {"00000000b"}
last_name {"苗字"}
first_name {"名前"}
last_name_kana {"ミョウジカナ"}
first_name_kana {"ナマエカナ"}
birthday {"20190101"}
telephone_number {"1234567890"}
end
# 購入者用データ factory名は「buyer」とします。
factory :buyer, class: User do
sequence(:nickname) { |i| "購入者_#{i}"}
sequence(:email) { |i| "byuer_#{i}@test.com"}
password {"00000000c"}
last_name {"苗字"}
first_name {"名前"}
last_name_kana {"ミョウジ"}
first_name_kana {"ナマエ"}
birthday {"20190101"}
telephone_number {"1234567890"}
end
end
続いてareas
FactoryBot.define do
# 出品者地域
factory :seller_area, class: Area do
name {"北海道"}
end
# 購入者地域
factory :buyer_area, class: Area do
name {"東京都"}
end
end
さらにaddresses
FactoryBot.define do
# 出品者住所
factory :seller_address, class: Address do
zip_code {"1234567"}
city {"city_1"}
number {"number_1"}
building {"building_1"}
last_name {"出品"}
first_name {"太郎"}
telephone_number {"03-1234-5678"}
#出品者のareaとuserを連携します。
association :area, factory: :seller_area
association :user, factory: :seller
end
# 購入者住所
factory :buyer_address, class: Address do
zip_code {"3214567"}
city {"city_2"}
number {"number_2"}
building {"building_2"}
last_name {"購入"}
first_name {"次郎"}
last_name_kana {"ジロウ"}
first_name_kana {"コウニュウ"}
telephone_number {"03-1234-5678"}
#購入者のareaとuserを連携します。
association :area, factory: :buyer_area
association :user, factory: :buyer
end
end
やっとitems
FactoryBot.define do
# 出品者用データ
factory :seller_item, class: Item do
sequence(:title) { |i| "product_#{i}"}
sequence(:description) { |i| "description_#{i}"}
price {1000.000}
#出品者のaddressと連携します。
association :address, factory: :selladdress
#出品者のaddress内にあるuserと連携します。
user {address.user}
end
end
最後にtrades
FactoryBot.define do
#購入用データ
factory :trade do
status_num {0}
#出品者のitemと連携します。
association :item, factory: :seller_item
#購入者のaddressと連携します。
association :address, factory: :buyer_address
#購入者のaddress内にあるuserと連携します。
user {address.user}
end
end
RSpecでテストコードを作ろう
tradeのテストコードはたったこれだけ
require 'rails_helper'
RSpec.describe Trade, type: :model do
describe "#create" do
#factoryデータを呼び出すときに、associationで関連付けした場合は、createを使います。
#buildを使うとテストそのものが失敗します。
let(:trade) { create(:trade) }
it "is valid trade" do
#ここでtradeを呼び出すことで、「let(:trade)」が実行されます。
expect(trade).to be_valid
end
end
end
ターミナルでテストを実行すると
こうなりました。
% bundle exec rspec spec/models/trade_spec.rb
Trade
#create
is valid trade
Finished in 1.12 seconds (files took 5.19 seconds to load)
1 examples, 0 failures
%
「is valid trade」と表示されたので、
登録テストが正常終了したことがわかりました。
まとめ
- 購入機能テスト用のfactoryデータは、出品者と購入者それぞれに必要な範囲で作成しなければならないこと。
- 他の開発者が作成したfactoryコードは流用しない方が良いこと。(そもそも購入機能を想定して作られたモノではないことがほとんど)
となりました。