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

时间:2013-12-18 16:28:48

标签: ruby-on-rails unit-testing rspec factory-bot

假设我有模型UserPost,用户has_many帖子和帖子belongs_to用户。

当我为Post编写规范时,我的第一直觉是写下这样的东西:

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创建一个具有ID的“假持久”@user,但@post.user仍会返回nil。当我测试与关联相关的事情时,build_stubbed是否比build有任何实际优势?

我想我可以使用build_stubbed存根@post.user,因此会返回@user ...这有什么理由可能是一个坏主意吗?

或者我应该只使用create并接受速度命中?

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

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

5 个答案:

答案 0 :(得分:18)

如果您不希望您的测试进入数据库,那么您就必须这样做。

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)

答案 1 :(得分:15)

简答

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

这将使@ post.user工作,而不会访问数据库。

长答案

我的建议是在before区块等待,直到您确定需要它为止。相反,为每个单独的测试构建所需的数据,并在找到时将重复提取到方法或新工厂。

另外,您是否真的需要在每次测试中引用用户?在每次测试中都有@user对其他开发人员说它在任何地方都很重要。

最后,假设您的帖子工厂也宣布了用户关联,那么当您执行post.user时,您将自动获得有效build_stubbed(:post)

答案 2 :(得分:9)

很容易忘记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

Source

答案 3 :(得分:1)

从工厂女孩的文档中,您可以在build工厂中找到与user相关联的策略post,如下所示:

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

这样您就可以build post而无需保存user

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

答案 4 :(得分:0)

快速解释差异: FactoryGirl.create将为它创建新对象和关联(如果工厂有任何关联)。它们都将保留在db中。此外,它将触发模型和数据库验证。 保存工厂后将调用(:build)和after(:create)之后的回调。在保存工厂之前,也会调用(:create)之前。

FactoryGirl.build不会保存对象,但如果工厂有关联,它仍会向数据库发出请求。它将仅触发关联对象的验证。 在构建工厂之后将调用(:build)之后的回调。

FactoryGirl.build_stubbed根本不会调用数据库。它创建并为对象分配属性,使其行为类似于实例化对象。它提供了一个假身份证和created_at。如果有的话,也将通过build_stubbed创建关联。它不会触发任何验证。

阅读完整解释here