RSpec 多対多関係 モデルテスト(例.Tagモデル)

#はじめに
rspecのTagのmodelテストをする際、中間テーブル(post_tag)を介したやり方に苦戦したため、
factory_botを用いて、多対多関係 (has_many through) のテスト作成方法をご説明致します。

##テーブル
`post`と`tag`の間に`post_tag`テーブルがある状態です。

<img width="1200" alt="スクリーンショット 2021-02-27 11.08.49.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/516540fe-97a9-2911-1e97-7e57b439a37e.png">

#到達点
以下の2点を達成する
・中間テーブルを介したmodelテストのFactoryBotを理解する
・中間テーブルを介したmodelテストの記述方法を理解する

##流れ
① 各モデルのvalidatesを確認
② FactoryBotの記述
③ modelテストの記述


#① 各モデルのvalidatesを確認
```ruby:app/models/post.rb
class Post < ApplicationRecord
  has_many :post_tags, dependent: :destroy
  has_many :tags, through: :post_tags

  validates :title,
            presence: true,
            length: { maximum: 60 }
  validates :body,
            presence: true,
            length: { maximum: 2000 }
end
```

```ruby:app/models/post.rb
class Tag < ApplicationRecord
  has_many :post_tags, dependent: :destroy
  has_many :posts, through: :post_tags

  validates :name, presence: true, length: { maximum: 50 }
end
```

```ruby:app/models/post.rb
class PostTag < ApplicationRecord
  belongs_to :post
  belongs_to :tag

  validates :post_id, presence: true
  validates :tag_id, presence: true
```


#② FactoryBotの記述
```ruby:app/spec/factories/post.rb
FactoryBot.define do
  factory :post do
    sequence(:title) { |n| "title-#{n}" }
    sequence(:body) { |n| "body-#{n}" }

    after(:create) do |post|
      create_list(:post_tag, 1, post: post, tag: create(:tag))
    end
  end
end
```
`after(:create)`を使用することで、post生成後に、tagとpost_tagが生成されます。
<br>

```ruby:app/spec/factories/tag.rb
FactoryBot.define do
  factory :tag do
    sequence(:name) { |n| "tag-#{n}" }
  end
end
```
`sequence`でユニークnameを生成できます。
<br>


```ruby:app/spec/factories/post_tag.rb
FactoryBot.define do
  factory :post_tag do
    association :post
    association :tag
  end
end
```
`association :post` `association :tag`とすることで、
post_tagのmodelテストにおいて
`let(:post_tag) { create(:post_tag) }`と記述するだけで
postとtagも生成できます。

ただし、associationは`has_many`側（今回の場合,post,tag)では記述せず、
`belong_to`側でのみ使用しましょう。
<br>

#③ modelテストの記述
```ruby:app/spec/requests/post.rb
RSpec.describe Post, type: :model do
  let(:post) { create(:post) }

  it "タイトル、本文、user_idがある場合、有効であること" do
    expect(post).to be_valid
  end

  it "user_idがない場合、無効であること" do
    post.user_id = nil
    expect(post).to be_invalid
  end

  describe "タイトル" do
    it "タイトルがない場合、無効であること" do
      post.title = nil
      expect(post).to be_invalid
      expect(post.errors[:title]).to include("を入力してください")
    end

    context "タイトルが60文字以下の場合" do
      it "有効であること" do
        post.title = "1" * 60
        expect(post).to be_valid
      end
    end

    context "タイトルが61文字以上の場合" do
      it "無効であること" do
        post.title = "1" * 61
        expect(post).to be_invalid
      end
    end
  end

  describe "本文" do
    it "本文がない場合、無効であること" do
      post.body = nil
      expect(post).to be_invalid
      expect(post.errors[:body]).to include("を入力してください")
    end

    context "本文が2000文字以下の場合" do
      it "有効であること" do
        post.body = "1" * 2000
        expect(post).to be_valid
      end
    end

    context "本文が2001文字以上の場合" do
      it "無効であること" do
        post.body = "1" * 2001
        expect(post).to be_invalid
      end
    end
  end
end

```

```ruby:app/spec/requests/tag.rb
RSpec.describe Tag, type: :model do
  let(:tag) { create(:tag) }

  describe "name" do
    it "タグ名がある場合、有効であること" do
      expect(tag).to be_valid
    end

    it "タグ名がない場合、無効であること" do
      tag.name = nil
      expect(tag).to be_invalid
      expect(tag.errors[:name]).to include("を入力してください")
    end

    context "タグ名が50文字以下の場合" do
      it "有効であること" do
        tag.name = "1" * 50
        expect(tag).to be_valid
      end
    end

    context "タグ名が51文字以上の場合" do
      it "無効であること" do
        tag.name = "1" * 51
        expect(tag).to be_invalid
        expect(tag.errors[:name]).to include("は50文字以内で入力してください")
      end
    end
  end
end

```
```ruby:app/spec/requests/post_tag.rb
RSpec.describe PostTag, type: :model do
  let(:post_tag) { create(:post_tag) }

  it "post_idとtag_idがある場合、有効であること" do
    expect(post_tag).to be_valid
  end

  it "post_idがない場合、無効であること" do
    post_tag.post_id = nil
    expect(post_tag).to be_invalid
  end

  it "tag_idがない場合、無効であること" do
    post_tag.tag_id = nil
    expect(post_tag).to be_invalid
  end
end
```


`association`によって`let(:post_tag) { create(:post_tag) }`が一文で済みました。
なお、itやcontext内の文章は、英語だとスペルミス等が出る可能性があるため、基本日本語にしております。

間違い等がありましたらご指摘の方よろしくお願いします。

#参考記事

[FactoryBot(FactoryGirl)チートシート](https://qiita.com/morrr/items/f1d3ac46b029ccddd017
)
[factory_girl で最低限知っておきたい4つの使い方](http://blog.livedoor.jp/sasata299/archives/51931175.html
)
[FactoryBot（旧FactoryGirl）で関連データを同時に生成する方法いろいろ](https://qiita.com/metheglin/items/47116ccbdb26aa00e034
)