2013-12-18 39 views
8

假設我有模型UserPost,用戶has_many帖子和帖子belongs_to用戶。使用FactoryGirl加速模型規格中的關聯 - 創建vs構建vs build_stubbed

當我寫Post一個規範,我的第一直覺就是寫這樣的:

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

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

但是,這將創建一個新的用戶 - 命中數據庫 - 爲每一個測試,這是要放慢速度。有沒有更好的方法來做到這一點,這將加快我的測試,並避免經常碰到數據庫?

據我瞭解,我不能用FactoryGirl.build :user,因爲,即使它不會碰到DB,該協會將不能正常工作,因爲@user不會有一個ID,因此@post.user將無法​​正常工作(它返回nil

我可以用FactoryGirl.build_stubbed :user它創造了一個「假堅持」 @user裏面確實有一個ID,但仍@post.user返回nil。當我測試與協會相關的事情時,build_stubbedbuild有什麼實際優勢?

我想我可以使用build_stubbed存根@post.user所以它返回@user ......有什麼理由這可能是一個壞主意?

或者我應該只使用create並接受速度命中?

我能想到的唯一另一種方法是在before(:all)塊中設置@user,這似乎是一個糟糕的主意。

以簡潔明瞭的方式編寫這些測試的最佳方式是什麼,避免了太多的數據庫查詢?

回答

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) 
15

簡答

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

這會讓@ post.user工作不會去訪問數據庫。

長的答案

我的建議是等待before塊,直到你確定你需要它。相反,您可以爲每個單獨的測試構建所需的數據,並在找到方法或新工廠時提取重複數據。

另外,您是否真的需要在每次測試中引用用戶?在每個測試中都有@user,對其他開發人員說它在任何地方都很重要。

最後,假設用戶關聯也在您的發佈工廠中聲明,那麼當您執行build_stubbed(:post)時,您將自動獲得工作post.user

8

可能很容易忘記create,buildbuild_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

1

從工廠女孩的文檔,你可以在post廠協會確定戰略builduser這樣的:

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

這樣節省您可以build一個postuser

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