giant robots smashing into other giant robots

Written by thoughtbot

readeharris

Testing Your Factories First

Have you ever added a seemingly innocent validation to a model, like so:

class User < ActiveRecord::Base
  validates_presence_of :first_name
end

And then had this happen?:

$ rake spec

If you’re using FactoryGirl, this can often happen because you haven’t updated your factory to respect the new validation.

Of course, the fix is simple:

# spec/factories.rb

FactoryGirl.define do
  factory :user do
    first_name 'Jonah'
  end
end

But how do you save yourself the time (and potential heart attack) of watching all those dreaded Fs go by?

Watching Over Your Factories

My old solution used to be adding a boilerplate test in each model’s unit spec, like so:

# spec/models/user_spec.rb

describe User do
  it 'has a valid Factory' do
    build(:user).should be_valid
  end
end

This is better than nothing, but it has two major problems.

  1. It’s not DRY. This code needs to be added to every unit spec.
  2. It’s slow. The whole model spec (and potentially many other specs) will have to run before we can address the failure.

Enter our savior, the factories_spec.rb:

# spec/factories_spec.rb

FactoryGirl.factories.map(&:name).each do |factory_name|
  describe "The #{factory_name} factory" do
    it 'is valid' do
      build(factory_name).should be_valid
    end
  end
end

Fancytime! Now all our factory specs are in one place, DRY as a desert.

Fast-Failing Factories

On to the second problem: we don’t want to wait for all our specs to run to deal with this error.

If you use rake to run your tests, one way to take care of this is by adding a pre-requisite to your rake spec task:

# Rakefile

desc 'Run factory specs.'
RSpec::Core::RakeTask.new(:factory_specs) do |t|
  t.pattern = './spec/factories_spec.rb'
end

task spec: :factory_specs

Now the :factory_specs task will need to run and succeed before the whole suite is run. If you have a bad factory, you’ll know right away with a nice failure message:

$ rake spec

No more panic attacks necessary.

Just one of the many new things I’ve learned in my first day as a thoughtbot apprentice!

jdclayton

Use Factory Girl’s build_stubbed for a Faster Test Suite

Want to speed up your test suite? Reduce the number of objects persisted to the database. With Factory Girl, this is really easy; instead of using build or create to instantiate your models with data, use build_stubbed!

build_stubbed is the younger, more hip sibling to build; it instantiates and assigns attributes just like build, but that’s where the similarities end. It makes objects look look like they’ve been persisted, creates associations with the build_stubbed strategy (whereas build still uses create), and stubs out a handful of methods that interact with the database and raises if you call them. This leads to much faster tests and reduces your test dependency on a database.

For example, let’s say we have an OrderProcessor that accepts instances of Order and CreditCard. It hits the Braintree API and returns a boolean value for if the charge actually happened.

class OrderProcessor
  def initialize(order, credit_card)
    @order = order
    @credit_card = credit_card
  end

  def process
    charge_successful? charge_customer
  end

  private

  def charge_successful?(result)
    # processes the result to determine if the result is valid,
    # operating on the order and credit_card instance variables to
    # add errors, send emails, or track Braintree's transaction id
  end

  def charge_customer
    # runs Braintree::Customer.sale() with the appropriate options
  end
end

None of this code hits the database. This allows us to write tests like:

describe OrderProcessor do
  let(:transaction_id) { '1234' }
  let(:order) { build_stubbed(:order) }
  let(:credit_card) { build_stubbed(:credit_card) }

  subject { OrderProcessor.new(order, credit_card) }

  context 'when the Braintree result is valid' do
    before do
      MockBraintree.stub_successful_customer_sale(transaction_id: transaction_id)
    end

    it 'assigns the transaction id to the order' do
      subject.process
      order.transaction_id.should == transaction_id
    end

    it 'returns true for #process' do
      subject.process.should be
    end

    it 'does not assign any errors to the credit card' do
      subject.process
      credit_card.errors.should be_empty
    end
  end

  context 'when the Braintree result is invalid' do
    before do
      MockBraintree.stub_unsuccessful_customer_sale
    end

    it 'does not assign the transaction id to the order' do
      subject.process
      order.transaction_id.should be_nil
    end

    it 'returns false for #process' do
      subject.process.should_not be
    end

    it 'assigns errors to the credit card' do
      subject.process
      credit_card.errors.should_not be_empty
    end
  end
end

Instead of creating twelve different records (at the minimum - if any of these factories have associations, you introduce more multipliers), we create none. This keeps the spec blazing fast.

Properly factored code should be small and concise. Code should typically depend less on the state of the data in relation to the database and more on its state in relation to other objects. In the above example, OrderProcessor only handles dealing with Braintree and dealing with its errors; it does not care about how this data is displayed to the user. That’s left for the integration tests, which should be hitting the database.

Although it’s not useful in every situation (there are cases where you’ll want data to exist, like when you’re testing uniqueness constraints or scopes), build_stubbed should be your go-to FactoryGirl method over build or create. Everyone running your test suite (yes, even yourself) will thank you.

gabebw

This week in open source

factory_girl

There’s a new version of factory_girl this week, as usual: 3.1.1 (aa81d2f).

Mark Rushakoff (mark-rushakoff) fixed a typo (4682ab0). Have I mentioned how much I love documentation fixes? Because I do.

Joshua Clayton (joshuaclayton) ensured that FactoryGirl.attributes_for works with has_many associations (50be545). He also refactored Strategies to be modules instead of classes, and added an Evaluation façade to make building strategies easier (89d5e94).

flutie

flutie got some love this week from Edwin Morris (ehmorris), who changed values from pixels to ems (fd26ed0) and these new-fangled CSS3 “rems”. REMs are relative to the base element’s size, not to the parent’s size (more here). I wish I’d had that back in the day.

paperclip

paperclip got a bunch of bug fixes from Prem Sichanugrist (sikachu). He fixed the content_type validator to allow blanks and nils (5eed1dc), ensured that code dealt with the content type, not the MimeType (3f1d30f), closed ALL the files (4f6d482), and removed unused code (yay!) (02eb725).

Sebastien Guignot (sguignot) fixed attachment.reprocess! when using Fog or S3 (9d1355b).

Kir Maximov (kir) fixed a problem with an incorrect content_type (3e98fc2).

shoulda-matchers

shoulda-matchers continues to improve. Gabe Berke-Williams (gabebw - that’s me!) added tests for Rails 3.2 (aff2824) and bumped rspec-rails (5baa056). As ever, the NEWS file is the place to watch for updates to functionality.

Aaron Gibralter (agibralter) fixed the ensure_length_of matcher to check for all possible I18n error messages (0a8e652), meaning our internationalized users aren’t left with false negatives in their tests.

Victor Pereira (vpereira) added the in_array method (e40e2cb) to the ensure_inclusion_of matcher, meaning you can do it { should ensure_inclusion_of(:attribute).in_array(%w[cat dog]) now.

gabebw

This Week in Open Source

clearance

Gabe Berke-Williams (gabebw - that’s me!) cleaned up the clearance Rakefile a bit (4f016db).

factory_girl

Joshua Clayton (joshuaclayton) released version 3.1.0 of factory_girl (f1d3018). For the full list of changes, see the NEWS file. Josh updated a few dependencies too (20becc9, 29157d6). Kristian Mandrup (kristianmandrup) added the ability to alias sequences, just like you can alias factories (f387e38, 178a7ab). To see how to use it, see the documentation (f013335) he added for it. I love documentation pull requests.

factory_girl_rails

Joshua Clayton (joshuaclayton) bumped factory_girl_rails to version 3.1.0 (4259e4c) to match factory_girl’s new version.

paperclip

Prem Sichanugrist (sikachu) released version 3.0.2 (240147e) of paperclip. Unfortunately, the NEWS file hasn’t been updated for 3.0.2 yet. Prem removed an obsolete generator, then added a test for it (a2a4c7a, 03700c8). Preston Guillory (pguillory) fixed a typo (853595a). Typo fixes are always welcome! Michael Galero (mikong) pluralized the table name in the migration generator to follow Rails convention (28e2d1b). And Rafael Mendonça França (rafaelfranca) removed init.rb, since plugins will be deprecated in Rails 4.0 (ae7b7c5).

shoulda-matchers

Gabe Berke-Williams (gabebw - me again!) finally released a new version of shoulda-matchers! Version 1.1.0 has a bunch of changes that you can see in the NEWS file. One of the noteworthy changes is that shoulda-matchers now depends on ActiveSupport >= 3.0.0 (c65e43a), meaning it’s Rails 3-only. We’ve been only testing against Rails 3 for a while, so this just makes it official. Gabe made a couple of documentation fixes (9203275, 8fcc3d2, a4edff0) and also cleaned up the code (5873502, 41088bc, 3039cc6, 820f216, 2e73b35, 457be62). Brendan Loudermilk (bloudermilk) added an accept_nested_attributes_for matcher (ee74222).

this-week-in-open-source

Finally, Gabe Berke-Williams (gabebw) fixed a little bug in this-week-in-open-source (which I used to generate this post!) to print missing directories before doing anything else (564c7c9).

dancroak

Short, explicit test setups

You probably know about this Factory Girl definition syntax:

FactoryGirl.define do
  factory :user do
    name 'Connie Customer'
  end
end

But did you know about this Factory Girl invocation syntax?

setup do
  @user = create(:user)
end

Or:

setup do
  @user = build(:user)
end

Or:

setup do
  post :create, user: attributes_for(:user)
end

It’s in there.

Configuration for Test::Unit / Shoulda:

class ActiveSupport::TestCase
  include FactoryGirl::Syntax::Methods
end

Configuration for RSpec:

RSpec.configure do |config|
  config.include FactoryGirl::Syntax::Methods
end

Configuration for Cucumber:

World FactoryGirl::Syntax::Methods

Written by .