DRY RSpec by generating specifications in a loop

Occasionally, when doing Behavior Driven Development with RSpec, I'll need to specify a number of cases where the internal structure of the spec is identical. Specifying a simple function, for example, might lead to code like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
context "My one adder" do
  setup do
    @processor = Adder.new(1)
  end
  
  specify "should add 1 and get 2" do
    @processor.add(1).should == 2
  end
  
  specify "should add -1 and get 0" do
    @processor.add(-1).should == 0
  end
  
  specify "should add 0 and get 1" do
    @processor.add(0).should == 1
  end
end

Given the information being conveyed, this is very verbose and also not DRY. (When writing specs, it's important to note that DRY is not the #1 priority—that's clarity—but overly "wet" code can still increase the chance of bugs.)

One way to reduce the length of the spec is to condense the expecations into a single specification block:

1
2
3
4
5
6
7
8
9
10
11
context "My one adder" do
  setup do
    @processor = Adder.new(1)
  end
  
  specify "should add numbers correctly" do
    @processor.add(1).should  == 2
    @processor.add(-1).should == 0
    @processor.add(0).should  == 1
  end
end

This introduces two disadvantages. First, a failure on any expectation in the spec will cease execution of the specify block, and thus all further failures will be masked. Second, the specdoc produced by the sample is not as clear in conveying the expectations for the object.

Thanks to a quick tip from David Chelimsky, it's easy to have my cake and eat it too:

1
2
3
4
5
6
7
8
9
10
11
12
context "My one adder" do
  setup do
    @processor = Adder.new(1)
  end
  
  expected_results = {-1 => 2, -1 => 0, 0 => 1}
  expected_results.each do |input, result|
    specify "should add #{input} and get #{result}" do
      @processor.add(input).should == result
    end
  end
end

This produces the ideal specdoc, is DRY, and will not allow one failure to mask another during a spec run.

0 Responses to “DRY RSpec by generating specifications in a loop”