24
21

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-03-25

はじめに

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

テーブル

posttagの間にpost_tagテーブルがある状態です。

スクリーンショット 2021-02-27 11.08.49.png

到達点

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

流れ

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

① 各モデルのvalidatesを確認

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
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
app/models/post.rb
class PostTag < ApplicationRecord
  belongs_to :post
  belongs_to :tag

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

② FactoryBotの記述

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が生成されます。

app/spec/factories/tag.rb
FactoryBot.define do
  factory :tag do
    sequence(:name) { |n| "tag-#{n}" }
  end
end

sequenceでユニークnameを生成できます。

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側でのみ使用しましょう。

③ modelテストの記述

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

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

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)チートシート
factory_girl で最低限知っておきたい4つの使い方
FactoryBot(旧FactoryGirl)で関連データを同時に生成する方法いろいろ

24
21
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
21