RSpec 다 대 다 관계 모델 테스트 (예 : 태그 모델)

소개



rspec의 태그 모델 테스트를 할 때 중간 테이블 (post_tag)을 통한 방식에 고전했기 때문에,
factory_bot을 이용하여 다대다관계(has_many through)의 테스트 작성 방법을 설명하겠습니다.

테이블


posttag 사이에 post_tag 테이블이 있는 상태입니다.



도달점



다음 2점 달성
· 중간 테이블을 통한 모델 테스트의 FactoryBot 이해
· 중간 테이블을 통한 모델 테스트 작성 방법 이해

흐름



① 각 모델의 validates를 확인
② FactoryBot의 기술
③ 모델 테스트 설명

① 각 모델의 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 로 고유 이름을 생성할 수 있습니다.

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의 모델 테스트에서let(:post_tag) { create(:post_tag) }post와 tag도 생성할 수 있습니다.

다만, asociation은 has_many 측(이번 경우,post,tag)에서는 기술하지 않고,belong_to 측에서만 사용합시다.

③ 모델 테스트 설명



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)로 관련 데이터를 동시에 생성하는 방법 여러가지

좋은 웹페이지 즐겨찾기