Dave Schweisguth in a Bottle

How many meanings of that can you think of?

Controlling database usage in RSpec and factory_girl, part 1: Choosing and allowing appropriate strategies

leave a comment »

When testing each layer of a Rails application, it’s important to create test model instances in the right way for that layer. Some specs need model instances that have been saved in the database, or that appear to have been; others need instances that have not. It’s also important to not use the database when you don’t have to, because every use makes your tests take a little bit longer.

This post, part 1 in a two-part series, lays out the appropriate use of factory_girl in specs of each layer of Rails, and gives a way to enforce that usage.

The type of model instance that an example needs depends partly on the layer of Rails that it tests:

  • Model specs usually need model instances that have been saved in the database. For example, to test a query method, you need to save something for the query to find. Sometimes an instance that hasn’t been saved is acceptable or even necessary, such as when testing attribute validation — an invalid object can’t be saved in the database.
  • Controller specs are best written to not use the database at all, with model methods stubbed to return whatever each example requires. This frees controller specs from worrying about the correctness of the model methods (which are already tested in model specs), and is also faster. However, model instances in controller specs need to look like they’re saved, because application code that uses them expects IDs. For example, if you call a path helper with an unsaved object, you’ll get an error like ActionController::UrlGenerationError: No route matches {:controller=>"users", :action=>"show", :id=>nil}.
  • Helper and view specs, like controller specs, should also not use the database, but need their test instances to look like they’ve been saved.
  • Routing specs don’t need model instances at all!
  • lib specs (that is, specs of code in the lib directory) likewise don’t need model instances, since lib is for low-level code that doesn’t depend on the rest of the app.
  • feature specs (and Cucumber features) always need saved model instances. They’re integration tests, so they must use the database, speed be damned.
  • And if you’ve defined your own layers, you’ll probably find that each of those layers has its own requirements for test model instances too.

factory_girl, the most popular model object factory gem, provides several methods for creating a model instance:

  • build creates an instance that hasn’t been saved in the database, just as if it had been created with ModelClass.new. You can save it yourself later if you want.
  • create creates an instance in the database, just as if it had been created with ModelClass.create!.
  • build_stubbed creates an instance that has not been saved in the database, but that does have an ID, so appears to have been saved. Unlike objects created with build, an object created with build_stubbed can’t be saved — if you call a method like save! on a stubbed object, it will raise an error.

So each factory_girl method is only appropriate in specs of some layers of Rails:

  • Model specs usually use create, and sometimes use build (and those methods’ _list versions), never build_stubbed.
  • Controller, helper and view specs use build_stubbed (and build_stubbed_list), never build or create. build_stubbed instances have the IDs that these specs expect, which is much more convenient than setting them manually. build_stubbed instances also help prevent database usage in these specs, by raising an error when you forget to stub a method that would save the instance in the database. (It’s still up to you to stub other database-using methods on your model instances.)
  • Routing and lib specs don’t need factory_girl at all.
  • feature specs always use create.

To prevent anyone from accidentally using a method in the wrong layer, instead of just doing

config.include FactoryGirl::Syntax::Methods
 

in your spec_helper.rb, include only the methods you need in each layer. Put something like this in spec/support/factories.rb

module MyApp
  module Factories
    module Model
      delegate :build, :build_list, :create, :create_list, to: FactoryGirl
    end

    module ControllerOrHelperOrView
      delegate :build_stubbed, to: FactoryGirl
    end

    module Feature
      delegate :create, :create_list, to: FactoryGirl
    end

  end
end
 

If you use attributes_for, decide what layers it’s appropriate in and put it there too.

In your spec_helper.rb, include each of those modules only in specs that need it:

config.include MyApp::Factories::Model, type: :model
%i(controller helper view).each do |type|
  config.include MyApp::Factories::ControllerOrHelperOrView, type: type
end
 

Now you’ll be unable to accidentally use the wrong instance type in an example, and unnecessary, slow database usage will be prevented.

Sounds great! Let’s run our controller and helper and view specs and admire our query-free test log! But what’s this? SELECTs everywhere? How can that be?

For the startling answer, tune in to Part 2: association strategy and unstubbed queries, floating ashore soon in a bottle near you.

Advertisement

Written by dschweisguth

June 21, 2014 at 12:27

Posted in Programming, Rails, Ruby, Testing

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: