Speeding up associations in the model specification with FactoryGirl - creating vs build vs build_stubbed

Say I have a User and Post model, a has_many user message, and a post belongs_to .

When I write the spec for Post , my first instinct is to write something like this:

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

But this will lead to the creation of a new user - getting into the database - for each individual test, which slows down the work. Is there a better way to do this to speed up my tests and avoid getting into the database often?

As I understand it, I can not use FactoryGirl.build :user , because although it does not get into the database, associations will not work properly, because @user will not have an identifier, and therefore @post.user will not work ( it returns nil .)

I could use FactoryGirl.build_stubbed :user , who created a "fake saved" @user that has an ID, but @post.user still returns nil. Does build_stubbed have any practical advantage over build when I test things related to associations?

Suppose I could use build_stubbed stub @post.user to return @user ... is there any reason this might be a bad idea?

Or do I just need to use create and accept the speed?

The only alternative I can think of would be to set @user in a before(:all) block, which looks like a bad idea.

What is the best way to write these tests in a clean, concise way that allows you to make too many database queries?

+8
ruby-on-rails unit-testing rspec factory-bot
source share
4 answers

If you do not want your tests to fall into the database, this is what you would need to do.

 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 

Note. Be careful when using before(:all) . It is not executed in a transaction. Therefore, everything you create in before(:all) will remain in the database and may cause a conflict with other tests

About FactoryGirl.build , it creates an object, but creates associations.

For example,

 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) 
+18
source share

Short answer

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

This will make @ post.user work without getting into the database.

Long answer

My recommendation would be to wait for the before block until you are sure you need it. Instead, create the data needed for each individual test, and extract duplicate methods or new factories as you find it.

Also, do you really need to refer to the user in each individual test? Having @user available in every test tells other developers that this is important everywhere.

Finally, assuming the user association is also declared in your factory message, you will automatically get a working post.user when you do build_stubbed(:post) .

+15
source share

It’s easy to forget the differences between create , build and build_stubbed . Here's a short link for those in the same situation (as this page is highly rated in search results).

 # Returns a User instance that 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 

A source

+8
source share

From the factory girl document, you can define the build strategy for user in the association in post factory as follows:

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

So you can build a post without saving user

 post = build(:post) post.new_record? # => true post.author.new_record? # => true 
+1
source share

All Articles