giant robots smashing into other giant robots

We are thoughtbot. We make web & mobile apps.

Tagged:

Comments (View)

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.

Tagged:

Comments (View)

Testing a before_filter

Recently, we implemented a feature that required a before_filter in ApplicationController and whitelisting some other controllers using skip_before_filter.

We couldn’t actually test something like this directly because callbacks aren’t really methods. They’re entirely used for their side effects so we can only test what happens when we invoke one. Let’s check out an example:

require 'test_helper'

class ApplicationControllerTest < ActionController::TestCase
  context 'ensure_manually_set_password' do
    setup do
      class ::TestingController < ApplicationController
        def hello
          render :nothing => true
        end
      end

      ActionController::Routing::Routes.draw do |map|
        map.hello '', :controller => 'testing', :action => 'hello'
      end
    end

    teardown do
      Object.send(:remove_const, :TestingController)
    end

    context 'when user is logged in' do
      setup do
        @controller = TestingController.new
      end

      context 'and user has not manually set their password' do
        setup do
          @user = Factory(:user, :manually_set_password => false)
          login_as @user
          get :hello
        end

        should 'redirect user to set their password' do
          assert_redirected_to new_password_path(@user.password_token)
        end
      end
    end
  end
end

Note the use of the double-colon prepended to the TestingController, which ensures the class is top-level, not an inner class of ApplicationControllerTest. That way we can just do :controller => 'testing' and not have to write :controller => 'application_controller_test/testing'. We also use the private method remove_const to remove the class after we’re done, so we don’t litter the namespace.

Co-written with Gabe Berke-Williams

Tagged:

Comments (View)

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

Tagged:

Comments (View)

Decoupling Data from Presentation

I’m really happy to see a resurgence in an understanding that writing integration tests that use classes and DOM structure to perform assertions within these tests is typically a bad idea. The DOM structure constantly changes but the data displayed (the stuff we want to assert is present on a page) typically doesn’t.

Why write tests against some markup that a designer may change, sometimes often if he’s iterating over a design? It’ll end up causing everyone headaches and frustration.

How can we mark up our documents without performing assertions against a deep nesting of classes and elements while ensuring our data is displayed properly?

Data attributes.

The browsers I care about support HTML5, so why not start using some of its capabilities to clean up some integration tests?

Imagine this scenario:

Scenario: See the site description on the homepage
  When I am on the homepage
  Then I should see the site description

Simple, straightforward, and each step is a similar level of abstraction.

Now imagine this markup:

<section class="primary-content">
  <header>
    <h1>Welcome to the site!</h1>
    <h2>Insert a witty tagline here</h2>
  </header>
  <p class="description">This product will make your life 100 times
  better.</p>
</section>

I could write a step like this:

Then "I should see the site description" do
  page.should have_selector("section.primary-content p.description",
                            text: "This product will make your life 100 times better.")
end

This step is asserting that there’s a section element with a class of primary-content who has a child paragraph tag with a class of description that contains the correct text. Classes change. Structure changes. This is begging to be rewritten.

<section data-role="primary-content">
  <header>
    <h1>Welcome to the site!</h1>
    <h2>Insert a witty tagline here</h2>
  </header>
  <p data-role="description">This product will make your life 100 times better.</p>
</section>

Then "I should see the site description" do
  page.should have_selector("[data-role='primary-content'] [data-role='description']",
                            text: "This product will make your life 100 times better.")
end

Now, the step is asserting that there’s an element with a role of description within an element with a role of primary-content that contains the correct text.

XHTML introduced a role attribute, but it’s not present currently in HTML5. Luckily, data attributes are even more flexible and we can use any conventions we like. data-role and data-state are two of my favorites.

Interested in how to write awesome Cucumber steps? Head to my Intro to Test-Driven Rails workshop on March 26th and March 27th where we’ll cover this and other awesome topics on writing great tests!