Factory Grabber

Factory grabber speeds up your tests by grabbing the nearest appropriate database records to suit your needs.

The idea is simple: most of the time, when using factories, you don't really care about the specific attributes, you just need a database record to play with. factory_grabber will 'grab' a matching record from the database if available or create any extra records if required. Less inserts to the database means faster tests.

At the moment, only "factory_girl":http://github.com/thoughtbot/factory_girl by thoughtbot is supported. If you'd like to see more factories supported please let me know (gavin@handyrailstips.com)

To install:

As a gem:

    sudo gem install git://github.com/GavinM/factory_grabber.git

    # environment.rb
    config.gem "thoughtbot-factory_girl", :lib => "factory_girl"
    config.gem "factory_grbber", :version => ">=1.0.2", :lib => false

As a plugin:

    script/plugin install git://github.com/GavinM/factory_grabber.git


To benefit from this gem, set use_transactional_fixtures=() to false in spec_helper.rb

Spec::Runner.configure do |config|
  config.use_transactional_fixtures = false
        config.use_instantiated_fixtures  = false
      config.fixture_path = RAILS_ROOT + '/spec/fixtures/'

Example Usage

First, add the following line to spec_helper.rb or test_helper.rb:

    require "factory_grabber"

Then, in your tests/specs:

    # to grab 1 comment:
    @comment = Grab.a_comment

    # to grab 1 article:
    @article = Grab.an_article

    #to grab 4 users named "John Smith":
    @users = Grab.four_users(:first_name => "John", :last_name => "Smith")

    #to grab 73 users with standard factory attributes:
    @users = Grab.seventy_three_users

Practical examples:

In a controller spec... describe "GET /posts/1" do


      before do
            # will create a record with these attributes if required, if not it will find the existing record
        @post = Grab.one_post :title => "This is my first post", :body => "This is the post body"

      def   do_get
        get :show, :id => @post

      it "should show the post title" do
        response.should include_text(/This is my first post/)

        it "should show the post body" do
            response.should include_text(/This is the post body/)


    describe "GET /posts?page=1" do

        # testing pagination


        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

        def do_get
            get :index, :page => 1

        it "should find the latest 10 posts" do
            assigns[:posts].should == Post.find(:all, :order => "created_at DESC", :limit => 10)


At the moment, all numbers between one and ninety_nine are supported. The general syntax for Grab methods is:


Performance gains

Here's a quick example of the possible performance gains:

Rehearsal --------------------------------------------------------------
Create 50 new factories      0.130000   0.200000   0.330000 (  6.785332)
Grab 50 separate factories   0.320000   0.020000   0.340000 (  0.332814)
Grab 50 factories at once    0.010000   0.000000   0.010000 (  0.012414)
----------------------------------------------------- total: 0.680000sec

                                 user     system      total        real
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)

(These results can be produced for your own environment by running the file "performance_test.rb" in factory_grabber/lib/spec/performance_test.rb)

Known Issues

At the moment, existing factories are found calling the model name. If your factory names do not match the name of the models then new factories will be created each time. eg.

    Factory(:admin_user, :class => :user) do |f|
        # ... etc ...

    Grab.an_admin_user # => will not find records from the database.


This project is still an infant - if the Ruby community find it useful I plan on adding a lot more. Please send any ideas/feedback to gavin@handyrailstips.com

Free to upload, edit, share, fork etc.
Gavin Morrice