假设我有模型User
和Post
,用户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,这似乎是一个坏主意。
以干净,简洁的方式编写这类测试以避免进行过多数据库查询的最佳方法是什么?
答案 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)
很容易忘记create
,build
和build_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
答案 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