商品出品画面を作る④RSpec(テスト)

#RSpecを使用し、バリデーションのテストとコントローラーのテストを行う
[商品出品画面を作る①へ](https://qiita.com/m-kawakami/items/8617ac3d9b3528ad0326)
## モデル
```ruby:item.rb
  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
```
## コントローラー
```ruby:items.controller.rb
  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で必要なデータを用意する

###商品
```ruby:factory/items.rb
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ディレクトリを用意し、その中にテストで使う画像を用意する

###ユーザー
```ruby:factory/users.rb
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
```
###カテゴリー
```ruby:factory/categories.rb
FactoryBot.define do
  factory :category do
    name {"hoge"}
    ancestry {"1/99"}
  end
end
```
カテゴリーは内容が少ないのでcategoryで用意しなくてもよかったかも・・・

#モデルのテスト
モデルはいろんな人が資料をだしているので、簡潔に画像部分だけ

```ruby:supec/models/item_spec.rb
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()の第二引数**に指定する

#テスト実行
```ruby:ターミナル
 bundle exec rspec ./spec/models/item_spec.rb
```
###バリデーションが無い時
![スクリーンショット 2020-04-12 20.34.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565886/82f1e1ed-e26e-1540-fa8e-7f6f0774769a.png)

###include()が空の時
![スクリーンショット 2020-04-12 20.35.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565886/6bb5bfac-1208-c0cd-87e9-454a0f9fe3d4.png)






#コントローラーのテスト
```ruby:supec/controllers/items_controller_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のデータ**
　となる

- ログインしている➡︎正しいページが表示されるか？
　割愛
- ログイン指定ない➡︎ログインページに遷移するか？
　割愛

```ruby:supec/controllers/items_controller_spec.rb
  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モデルの数が１増えるか？

- ログインしている➡︎保存できる➡︎トップページにリダイレクトする
**expect(response).to redirect_to(root_path)**
response➡︎createアクションを動かした後に表示される画面
redirect_to(root_path)➡︎root_path(トップページ)にリダイレクトするか？

- ログインしている➡︎保存できない➡︎レコードの数が変わらない
- ログインしている➡︎保存できない➡︎出品画面に戻る
- ログインしていない➡︎ログインページに遷移する



#テスト実行
```ruby:ターミナル
 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を使って形をよく確かめる！！