Cleaner RSpec Controller Specs
RSpec, dev, ruby, testingTesting 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.
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:
1
2
3
4
5
6
7
8
9
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!!
1
2
3
4
5
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:
so you can assert that your products relation is unchanged by adding the desired scope. This isn’t always the case though. Some order
s and where
s just keep getting tacked onto the end of the relation… so it falls back to an array comparison, and that is unawesome. Anywho, the final product is:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
RSpec::Matchers.define :have_scope do |scope_name, *args|
match do |actual|
actual.send(scope_name, *args) == actual
end
failure_message_for_should do |actual|
"Expected relation to have scope #{scope_name} #{args.present? ? "with args #{args.inspect}" : ""} but it didn't " + actual.to_sql
end
failure_message_for_should_not do |actual|
"Expected relation not to have scope #{scope_name} #{args.present? ? "with args #{args.inspect}" : ""} but it didn't " + actual.to_sql
end
description do
"have scope #{scope_name} #{args.present? ? "with args #{args.inspect}" : ""}"
end
end
This has helped me write legible and concise specs, mileage my vary.