使用FactoryGirl加速模型规范中的关联 - create vs build vs build_stubbed

12

假设我有模型UserPost,一个用户拥有多篇文章(has_many),一篇文章属于一个用户(belongs_to)。

当我为Post编写规范(spec)时,我的第一反应是编写类似以下内容:

before do
  @user = FactoryGirl.create :user
  @post = @user.posts.new(title: "Foo", content: "bar)
end

... tests for @post go here ...

但是这会为每个单独的测试创建一个新用户 - 并对数据库进行操作 - 这将减慢速度。有没有更好的方法可以加快我的测试速度,避免经常连接到数据库?

据我所知,我不能使用FactoryGirl.build :user,因为即使它不会连接到数据库,关联也不会正常工作,因为@user没有ID,所以@post.user无法工作(返回 nil )。

我可以使用FactoryGirl.build_stubbed :user来创建“虚假持久化”的@user,它确实具有ID,但@post.user仍然返回 nil 。当我测试与关联相关的内容时,build_stubbed是否比 build 更实用?

我想我可以使用build_stubbed存根 @post.user ,以便它返回 @user ...这样做有什么不好的原因吗?

还是应该使用create并接受速度降低?

我能想到的唯一其他选择是在before(:all)块中设置 @user ,这似乎不是一个好主意。

以清晰、简洁的方式编写这些测试的最佳方法是什么,以避免进行过多的数据库查询?

5个回答

23

如果您不希望您的测试影响数据库,那么您需要做的就是这样。

before do
  @user = FactoryGirl.build_stubbed :user
  @post = FactoryGirl.build_stubbed :post
  @user.stub(:posts).and_return([@post])
  @post.stub(:user).and_return(@user)
end

注意:在使用before(:all)时要小心。它不会在事务中执行。因此,在before(:all)中创建的任何内容都将留在数据库中,并可能与其他测试导致冲突。

关于FactoryGirl.build,它会构建对象,但是也会创建关联项。

例如:

factory :user do
  association posts
end

FactoryGirl.build(:user) #this creates posts in the database even though you are only building the parent object(user)

17

简短回答

@user = FactoryGirl.build_stubbed(:user)
@post = FactoryGirl.build_stubbed(:post, :user => @user)

这样做可以让@post.user在不触及数据库的情况下工作。

长答案

我的建议是在确实需要它之前等待before块。相反,为每个单独的测试构建所需的数据,并在发现重复时将其提取到方法或新工厂中。

另外,你真的需要在每个测试中引用用户吗?在每个测试中都有@user可用表示它在任何地方都很重要。

最后,假设用户关联也在您的帖子工厂中声明,当您执行build_stubbed(:post)时,您将自动得到一个可工作的post.user


11

很容易忘记createbuildbuild_stubbed之间的区别。以下是一个快速参考,供处于相同情况(因为此页面在搜索结果中排名很高)的人使用。

# Returns a User instance that's not saved (does not write to DB)
user = build(:user)

# Returns a saved User instance (writes to DB)
user = create(:user)

# Returns a hash of attributes that can be used to build a User instance
attrs = attributes_for(:user)

# Returns an object with all defined attributes stubbed out
stub = build_stubbed(:user)

# Passing a block to any of the methods above will yield the return object
create(:user) do |user|
  user.posts.create(attributes_for(:post))
end

来源


1

从factory girl文档中,您可以识别出在与post工厂相关联的user策略中使用build的方式:

factory :post do
  association :user, factory: :user, strategy: :build
end

这样你可以创建一个带有 用户 但无需保存的 post

post = build(:post)
post.new_record?        # => true
post.author.new_record? # => true

1
快速解释一下区别: FactoryGirl.create 将创建一个新的对象和它的所有关联(如果工厂有任何关联)。 它们都将被持久化在数据库中。 此外,它将触发模型和数据库验证。 回调 after(:build) 和 after(:create) 将在工厂保存后调用。同时,在工厂保存之前会调用 before(:create)。
FactoryGirl.build 不会保存对象,但如果工厂有关联,则仍会向数据库发出请求。 它仅为关联对象触发验证。 回调 after(:build) 将在构建工厂后调用。
FactoryGirl.build_stubbed 根本不会调用数据库。 它创建并分配属性到一个对象上,使其像一个实例化的对象。 它提供一个虚假的 id 和 created_at。 如有关联,也将通过 build_stubbed 创建。 它不会触发任何验证。
阅读完整说明这里

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接