如何在RSpec中存根has_many关联

时间:2018-11-21 12:10:15

标签: ruby rspec

我试图在RSpec中存根has_many关联,因为构建记录是如此复杂。我想使用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 :(得分:1)

您为什么不使用FactoryBot associations

#!/usr/bin/env python
from distutils import sysconfig
import os
site_packages_path = sysconfig.get_python_lib()

PTH_FILENAME = 'MyApp.pth'
# Change here to your project home dir
PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

relative_paths = [
  '.',
  'plugins',
  'src/achilterm',
  'src/addicted',
  'src/naghelp',
  'src/textops',
  'src',
]

absolute_paths = [ os.path.join(PROJECT_DIR,p) for p in relative_paths ]

with open(os.path.join(site_packages_path,PTH_FILENAME),'w') as fh:
    for p in absolute_paths:
        print 'Installing path : %s ...' % p
        fh.write('%s\n' % p)

这允许您执行以下操作:

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?