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)

What would happen if you ran bundle update right now?

Is there a bundle command to tell me what would be updated with bundle update, without actually making those updates?

As it turns out there is! Bundler 1.1 introduces a new command:

bundle outdated
    Show all of the outdated gems in the current bundle.  
    This will give you a report of gems that have newer versions available.

By itself, this will list all of the gems in your Gemfile.lock that have newer versions, and what the current and latest versions are.

This gives a good preview of what you are up against if you want to get your gems up to date. From there, use git commits to make incremental changes.

Putting it to use

Today I created an app with the suspenders gem, and noticed it is running on Rails 3.1.1, which I wanted to upgrade to 3.2.2.

I also saw a few other gems with version attributes:

gem 'sass-rails',   '~> 3.1.4'
gem 'coffee-rails', '~> 3.1.1'
gem 'uglifier', '>= 1.0.3'
gem 'rspec-rails', '~> 2.6.1'
gem 'cucumber-rails', '1.1.0'
gem 'capybara-webkit', '~> 0.7.1'

First I ran rake to confirm all tests were passing, bundle update to make sure the gems were all up to date with the versions specified, and created an initial commit.

Next I upgraded to rails 3.2.2 and reran bundle update, and found out (after a couple itterations) that sass-rails and coffee-rails had to be updated too, because of there dependance on ActiveSomethingOrOther. Updating these three gems to their latest versions allowed bundle update to do it’s thing.

I reran my tests, and did ran git diff Gemfile.lock to see exactly what was new. For giggles I reran bundle outdated and rejoiced at the shrinking list. Time to commit with a message about the gem version changes.

From here it was rinse and repeat, checking rubygems.org and looking at the dependencies, making small changes, bundling, raking, committing.

At the end of the road I had a good series of commits spelling out exactly what was needed to get to my goal of a fully updated rails 3.2.2 environment.

If you run bundleoutdated and all you see is:

Outdated gems included in the bundle:  
* sprockets (2.4.0 > 2.1.2)  

Then you are doing it right!

Have a cookie!

Read more about what’s new with bundler 1.1 from Pat Shaughnessy.

Tagged:

Comments (View)

Paperclip is Going Three-Point-Oh Nelly

The new version of your favorite file upload library, Paperclip 3.0.0, has been released earlier today. We changed a lot of internals to make Paperclip better. These changes lay the groundwork for the shiny, new features that will come in the future.

Goodbye, Rails 2.3.x and Ruby 1.8.7

Ruby 1.8.7 and Ruby on Rails 2.3.x are both reaching end of life now, so this is a good time to drop support for them. This let us remove some code and introduce a new validator syntax (below.)

If you’re still using Ruby on Rails 2.3.x or Ruby 1.8.7, you should lock Paperclip to version 2.x by doing this:

gem "paperclip", "~> 2.7"

In some rare case that there’s a critical bug in Paperclip 2.7.x, we’ll release a patch version under that minor version. However, you’re advised to upgrade to a newer version of Ruby and Rails as soon as you can.

Modularized Internals

We’re working to extract functionality into modules. We’re not done with this yet, and it is an ongoing process. If you’ve freedom patched Paperclip, you’ll need to look out for this as some of the methods might have new names or locations.

If you are developing a plugin for Paperclip—e.g. custom storage, a processor, or a validator—we would love to hear from you. We want to expose hooks to help you develop your plugin and reduce the risk of breaking things in the future.

Default attachment path has been changed

When you’re installing the new version of Paperclip, you’ll be greeted with this upgrading message:

##################################################
#  NOTE FOR UPGRADING FROM PRE-3.0 VERSION       #
##################################################

Paperclip 3.0 introduces a non-backward compatible change in your attachment
path. This will help to prevent attachment name clashes when you have
multiple attachments with the same name. If you didn't alter your
attachment's path and are using Paperclip's default, you'll have to add
`:path` and `:url` to your `has_attached_file` definition. For example:

    has_attached_file :avatar,
      :path => ":rails_root/public/system/:attachment/:id/:style/:filename",
      :url => "/system/:attachment/:id/:style/:filename"

Going forward, the default URL for your attachment will be /system/:class/:attachment/:id_partition/:style/:filename. This will prevent filename collisions and will reduce the number of files in a single directory.

Rails 3 Validators

All of the validators have been rewritten to take the full advantage of Rails 3. They’re now in their own Validator class, and can be passed into validates or validates_with like any other Active Model validator:

validates :avatar, :attachment_presence => true
validates_with AttachmentPresenceValidator, :attributes => :avatar

We are also introducing a validates_attachment method. This method is almost the same as the validates method except you can skip the :attachment_ prefix. We’ve found this helpful when defining multiple validators on a single attachment:

validates_attachment :avatar, :presence => true,
  :content_type => { :content_type => "image/jpg" },
  :size => { :in => 0..10.kilobytes }

Going forward

I would like to thank our contributors and all of the other users who are helping to make Paperclip better and better. Please give this new version a try. If you find a bug, please file an issue, or better, you can also help us fixing it.

We’re thrilled to introduce the new Paperclip, and we hope that you’ll like them.