【Rails】RSpecでのテスト時に詰まったところ～画像の添付、postリクエスト時のパラメーター付加など～

## はじめに
ここまる2日かけてRSpecを使ったテストコードを書いていました。
その中で、’この内容をテストしたいけどどう書いたらいいんだろう…？’と思うものがたくさんあったので、まとめておきます。

## 少し悩んだテスト内容と記述方法
### Model Spec
#### user_idとpost_idのペアにつけている一意性制約のテスト
```ruby
require 'rails_helper'

RSpec.describe Like, type: :model do
  context '同じポストにいいねしようとした場合' do
    let!(:user) { create(:user) }
    let!(:post) { create(:post) }

    let!(:like1) { create(:like, user: user, post: post) }
    let!(:like2) { build(:like, user: user, post: post) }

    before do
      like2.save
    end

    it 'いいねされない' do
      expect(like2).not_to be_valid
      expect(like2.errors[:user_id]).to include("has already been taken")
    end
  end
end
```
- `like1`を`create`したうえで、`like2`を`build`して`save`する流れを取っています
- `like2`を`create`しない理由は、そうしてしまうと処理が`like2`を`create`する時点で止まってしまい、テストしたい内容を確認できないためです

#### 画像の添付の有無でバリデーションが効くかを試したい
```ruby
# factories/posts.rb

FactoryBot.define do
  factory :post do
    caption { Faker::Lorem.characters(number: 50) }
    association :user

    trait :with_image do
      after(:build) do |post|
        post.images.attach(
          io: File.open(Rails.root.join('app/assets/images/arror.png')),
          filename: 'arror.png',
          content_type: 'image/png'
        )
      end
    end
  end
end
```
```ruby
# post_spec.rb

require 'rails_helper'

RSpec.describe Post, type: :model do
  context '正しい情報が与えられた場合' do
    let!(:post) { build(:post, :with_image) }

    before do
      post.save
    end

    it '投稿が保存される' do
      expect(post).to be_valid
    end
  end

  context '画像が添付されていない場合' do
    let!(:post) { build(:post) }

    before do
      post.save
    end

    it '投稿が保存さない' do
      expect(post).not_to be_valid
      expect(post.errors.messages[:images][0]).to eq("can't be blank")

    end
  end
end
```
- ポイントは以下の箇所です
- ```ruby
     trait :with_image do
      after(:build) do |post|
        post.images.attach(
          io: File.open(Rails.root.join('app/assets/images/arror.png')),
          filename: 'arror.png',
          content_type: 'image/png'
        )
      end
    end
    ```
- `trait`をつかうと、属性やオプションのようなものを定義し、モデルのインスタンス作成時につけたり外したりできます
- 今回の場合は　`with_image`という`trait`を定義し、画像付きのpostと画像なしのpostをそれぞれテストしています
- `after(:build)`はファクトリで使えるコールバックの一つのようです
- 今回の場合は`post`が`build`された際に、画像ファイルを添付するような動きをしています
- ちなみに、`create`は`build` + `save` の動きをするようなので、`create(:post, :with_image)`でも使えます


### Request Spec
#### postリクエストにパラメーターを付加する方法
```ruby
post path, params: { model: { key: val } }
```
具体例
```ruby
post post_like_path(post_id: test_post.id), params: { like: { post_id: test_post.id } }
```

- 属性？として`params: { model: { key: val } }`でパラメーターを付加できる
- postリクエストを受け取る場合、Controller側でストロングパラメーターを設定していることも多いと多いと思うので、その形式に合わせて書く、とするとわかりやすそうです

#### postリクエスト時に画像の添付をする方法
```ruby
  context 'ログインしている&&プロフィールが設定されている状態で記事を投稿する場合' do
    let!(:image_path) { Rails.root.join('app/assets/images/arror.png') }
    let!(:image_file) { fixture_file_upload(image_path, 'image/png') }

    describe "post /post" do
      it "ポストが作成され、ルートページに遷移する" do
        post posts_path, params: { post: { caption: 'hogehoge', images: [image_file] } }
        .
        .
        .
      end
    end
  end
```
- ActiveStratgeを使う場合にはアップロードの用の処理を別途記載する必要がありそうでした
- 具体的には以下の箇所です
- ```ruby
    let!(:image_path) { Rails.root.join('app/assets/images/arror.png') }
    let!(:image_file) { fixture_file_upload(image_path, 'image/png') }    
    ```
&emsp;
- `fixture_file_upload`はテスト時にファイルをアップロードするためのヘルパーメソッドのようです
- 第1引数に画像ファイルまでのパス、第2引数にファイル形式を指定します

#### モデル名がPostのときの注意点…
- なにげに解決に一番時間がかかったのはこれでした…
- 詳細はこちらの記事が大変わかりやすかったです
    - https://qiita.com/kurawo___D/items/50d260546255526c734f
- 要するに、モデル名を`Post`、テスト時のインスタンス名も`post`にしてしまっていたので、リクエストとしてpostとインスタンスとしての`post`をRSpec側が混同してしまったようでした


## おわりに
初学者の自身にとっては一つ一つつまずく点でしたが、テストが通ったときの快感はたまらないものがありました笑

今回は最後にまとめてテストしてしまったせいで無駄に時間を使ってしまいました。
今後はプルリクの前にテストをして問題ないものをマージしていくという流れを取っていきます。