I use factories instead of fixtures when testing. If you haven’t already discovered factories, check out this Railscast: Factories not Fixtures.
Now that you’re up to speed: I often find when writing tests that I simply want any record. Here’s a quick example scenario from a controller spec:
describe CommentsController do
describe "POST /posts/1/comments" do
before do
@post = Factory :post
@comment_attributes = Factory.attributes_for(:comment, :post => @post)
end
def do_post
post :create, :comment => @comment_attributes, :post_id => @post.id
end
it "should create a new comment for @post " do
lambda { do_post }.should change {@post.comments.count}.by(1)
end
it "should redirect to @post" do
do_post
response.should redirect_to(post_path(@post))
end
end
end
In this example, we’re creating a new post record even although there may already be several posts in the database. Since this test is only asserting that a new comment has been created for @post
it doesn’t really matter what the post’s specific attributes are. All we care about is that it’s a valid post we can create comments for.
Inserting new records to the database is usually slower than simply retrieving an existing record. As a result, constantly creating factories when we already have appropriate records means our tests are slower and more inefficient than they should be.
Factory Grabber
I recently published a gem which addresses this issue. To check it out, simply run:
sudo gem install git://github.com/bodacious/factory_grabber.git
Factory Grabber is intended to be used with Factory Girl by ThoughtBot.
To use factory grabber in your tests/specs simply call the number of records and the model name as a method on Grab like so:
# return 47 individual comment records
@comments = Grab.forty_seven_comments
# will return 9 user records each with last_name "Smith"
@smiths = Grab.nine_users(:last_name => "Smith")
# return one record
@user = Grab.a_user
@article = Grab.an_article
@post = Grab.one_post
If there are appropriate records already in the database, Grab finds them. If there are not appropriate records, Grab will create them using the factories you’ve already defined.
For a practical example:
describe "GET /posts/1" do
integrate_views
before do
@post = Grab.one_post :title => "This is the post title", :body => "This is the post's body"
end
def do_get
get :show, :id => @post
end
it "should show the post title" do
do_get
response.should include_text(/This is the post title/)
end
it "should show the post body" do
do_get
response.should include_text(/This the post's body/)
end
end
# test pagination
describe "GET /posts?page=1" do
integrate_views
before do
# ensures there are at least eleven Post records
# if there are less than eleven, new posts are created
# if there are eleven or more no posts are created
Grab.eleven_posts
end
def do_get
get :index, :page => 1
end
it “should find the latest 10 posts” do
do_get
assigns[:posts].should == Post.find(:all, :order => “created_at DESC”, :limit => 10)
end
end
Here’s example of the performance boots you can achieve:
user system total real(secs)
Create 50 new factories 0.100000 0.200000 0.300000 ( 6.354282)
Grab 50 separate factories 0.300000 0.000000 0.300000 ( 0.310373)
Grab 50 factories at once 0.020000 0.000000 0.020000 ( 0.011400)
In this case, grabbing 50 existing records is almost 20 times faster than creating 50 new factories!
Just make sure you turn off transactional fixtures in your test_helper.rb or spec_helper.rb files:
Spec::Runner.configure do |config|
config.use_transactional_fixtures = false
config.use_instantiated_fixtures = false
config.fixture_path = RAILS_ROOT + '/spec/fixtures/'
end
This gem is still in it’s infancy, I’d welcome any feedback/suggestions.