super awesome

Tommy

Cleaner Rspec Controller Specs

Ruby On Rails, RSpec

Testing is an important part of software development. Having a solid test suite allows you develop more confidently – enable you to make large refactorings without fear of unintended consequences. I test almost all the code I write. 1

Recently, I found a bug that allowed users to see ‘inactive’ products; so I wrote a test to prevent that. I’ve seen lots of specs that test active products like so:

it "scopes products to active and available" do
    not_active = Factory(:product, :active => false)
    not_available = Factory(:product, :available => false)
    active_available = Factory(:product, :active => true, :available => true)
    get :index
    assigns(:products).should_not include(not_active)
    assigns(:products).should_not include(not_available)
    assigns(:products).should include(active_available)
end

Test like this are wordy and can lead to a ton of factoried objects. There’s got to be a better way!!

it "scopes products to active and available" do
    get :index
    assigns(:products).should have_scope(:active)
    assigns(:products).should have_scope(:available)
end

Ahhh how refreshing… See that have_scope method there? That matcher is accomplished by applying the scope to the set of products and asserting that it should be the same ActiveRecord::Relation. In most cases:

ActiveRecord::Relation + scope == ActiveRecord::Relation + scope + scope

so you can assert that your products relation is unchanged by adding the desired scope to it, like so:

RSpec::Matchers.define :have_scope do |scope_name, *args|
  match do |actual|
    actual.send(scope_name, *args) == actual
  end

  failure_message do |actual|
    "Expected relation to have scope #{scope_name} #{args.present? ? "with args" : ""} but it didn't" + actual.to_sql
  end

  negative_failure_message do |actual|
    "Expected relation not to have scope #{scope_name} #{args.present? ? "with args" : ""} but it didn't" + actual.to_sql
  end
end

This has helped me write legible and concise specs, mileage my vary.