ショッピングサイトアプリの購入機能テスト(model)をRSpecとFactorybotで構築してみた。

## はじめに

ショッピングサイトアプリ(疑似フリマアプリ)を作成していて、購入機能を担当することになりました。
「RSpecでmodelのテストコードを作成せよ」とのお達しがありましたので、取り組んでみました。

その結果、associationで「belongs_to」の関係にあるモデルの連携に
ハマりましたので、備忘録もかねて投稿します。

## 前提

ここでは支払処理が完了した後に購入記録を登録する機能のテストを説明しています。
支払機能(payjpｌなどのAPI)について触れませんのでご了承ください。

## 参考記事

[【FactoryBot】associationの使い方](https://qiita.com/Ryoga_aoym/items/741c57e266a9d811a2d4)

## 開発環境

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の構築が済んだものとします。

<img width="1163" alt="スクリーンショット 2020-03-25 22.19.32.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532721/ed1b6c5d-e7e2-e2dd-6555-fe1f8fd71c05.png">

一見するとわかりにくいかもしれませんが、
このような考え方になります。

1. 出品者
    - ユーザーを登録する。(user)
    - 発送元住所を登録する。(address)
    - 売りたいモノを出品する。 (item)

1. 購入者
    - ユーザーを登録する。(user)
    - 発送先住所を登録する。(address)
    - 出品されたモノを購入する。 (trade)

ここで注意しなければならないことは

- userとaddressは**出品者と購入者それぞれに必要である**こと。
- itemは**出品者のみでがuserとaddressに従属する**こと。
- tredeは**出品者のitemと、購入者のuserとaddressが必要である**こと。

となります。

他の開発者が作成したfactoryデータコードを流用しようとしましたが、出品者と購入者の２種類を同時に
テストするように想定されていないため、使い回しができませんでした(横着をしてはいけない)。

よって、購入機能専用に出品者と購入者のコードを書くことにしました。

## Factorybotでテストデータを作ろう

まずはusers

```ruby:spec/factories/users.rb
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

```ruby:spec/factories/areas.rb
FactoryBot.define do
  # 出品者地域 
  factory :seller_area, class: Area do
    name {"北海道"}
  end

  # 購入者地域 
  factory :buyer_area, class: Area do
    name {"東京都"}
  end
end
```

さらにaddresses

```ruby:spec/factories/addrsses.rb
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

```ruby:spec/factories/items.rb
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

```ruby:spec/factories/trades.rb
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のテストコードはたったこれだけ

```ruby:spec/models/trade_spec.rb
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
```

## ターミナルでテストを実行すると

こうなりました。

```terminal:terminal
% 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コードは流用しない方が良いこと。(そもそも購入機能を想定して作られたモノではないことがほとんど)

となりました。