Как заглушить ассоциацию has_many в RSpec - PullRequest
0 голосов
/ 21 ноября 2018

Я пытаюсь заглушить ассоциацию has_many в RSpec, потому что создание записей очень сложно.Я хочу определить, у какого автора есть научная книга, используя Author#has_science_tag?.

Модель

class Book < ApplicationRecord
  has_and_belongs_to_many :tags
  belongs_to :author
end

class Tag < ApplicationRecord
  has_and_belongs_to_many :books
end

class Author < ApplicationRecord
  has_many :books

  def has_science_tag?
    tags = books.joins(:tags).pluck('tags.name')
    tags.grep(/science/i).present?
  end
end

RSpec

require 'rails_helper'

RSpec.describe Author, type: :model do
  describe '#has_science_tag?' do
    let(:author) { create(:author) }

    context 'one science book' do
      example 'it returns true' do
        allow(author).to receive_message_chain(:books, :joins, :pluck).with(no_args).with(:tags).with('tags.name').and_return(['Science'])
        expect(author.has_science_tag?).to be_truthy
      end
    end
  end
end

В этом случае использование receive_message_chain - хороший выбор?Или гася has_many ассоциации плохая идея?

1 Ответ

0 голосов
/ 21 ноября 2018

Почему вы не используете FactoryBot associations?

FactoryBot.define do
  # tag factory with a `belongs_to` association for the book
  factory :tag do
    name { 'test_tag' }
    book

    trait :science do
      name { 'science' }  
    end
  end

  # book factory with a `belongs_to` association for the author
  factory :book do
    title { "Through the Looking Glass" }
    author

    factory :science_book do
      title { "Some science stuff" }

      after(:create) do |book, evaluator|
        create(:tag, :science, book: book)
      end
    end
  end

  # author factory without associated books
  factory :author do
    name { "John Doe" }

    # author_with_science_books will create book data after the author has
    # been created
    factory :author_with_science_books do
      # books_count is declared as an ignored attribute and available in
      # attributes on the factory, as well as the callback via the evaluator
      transient do
        books_count { 5 }
      end

      # the after(:create) yields two values; the author instance itself and
      # the evaluator, which stores all values from the factory, including
      # ignored attributes; `create_list`'s second argument is the number of
      # records to create and we make sure the author is associated properly
      # to the book
      after(:create) do |author, evaluator|
        create_list(:science_book, evaluator.books_count, authors: [author])
      end
    end
  end
end

Это позволяет вам:

create(:author).books.count # 0
create(:author_with_science_books).books.count # 5
create(:author_with_science_books, books_count: 15).books.count # 15

Таким образом, ваш тест становится:

RSpec.describe Author, type: :model do
  describe '#has_science_tag?' do
    let(:author_with_science_books) { create(:author_with_science_books, books_count: 1) }

    context 'one science book' do
      it 'returns true' do
        expect(author_with_science_books.has_science_tag?).to eq true
      end
    end
  end
end

И вы также можете рефакторинг Author#has_science_tag?:

class Author < ApplicationRecord
  has_many :books

  def has_science_tag?
    books.joins(:tags).where("tags.name ILIKE '%science%'").exists?
  end
end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...