我试图在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关联是个坏主意?
答案 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?