I have been using rspec for for 8 months and I really wanted something like assert_difference in my specs. Seeing as its really easy to write custom matchers in rspec, I whipped one up really quick that lets me do this:
lambda { User.count }.should be_different_by(1) { User.create }
Which turns out to be the same thing as:
orig_count = User.count
User.create
User.count.should ==(orig_count + 1)
Which at first glance you may think is not even worth it, but when you have 50+ create actions and in your controller specs your doing at least 1 check for the count to go up (or stay the same) then you can start to see the real benefits of be_different_by. I count 110 uses of be_different_by in my current project. Thats 220 saved lines of code, and, I think, 110 specs that expose their purpose more clearly. Here is the code for be_different_by:
module Spec
  module Matchers
   
    class BeDifferentBy
      
      attr_reader :difference, :lamb, :target, :initial, :after
      
      def initialize(difference, b)
        @difference, @lamb = difference, b
      end
      
      def matches?(target)
        @target = target
        @initial = target.call
        @lamb.call
        @after = target.call
        (@initial + @difference) == @after
      end
      
      def failure_message
        "expected initial value of <#{@initial}> to be <#{(@initial + @difference)}> but was <#{@after}>"
      end
      
      def negative_failure_message
        "expected initial value of <#{@initial}> NOT to be <#{(@initial + @difference)}> but was <#{@after}>"
      end
    end
    
    def be_different_by(difference, &b)
      BeDifferentBy.new(difference, b)
    end
  end
  
  
end
And here is the be_different_by spec (yes I am spec'ing a custom matcher, sue me):
describe "BeDifferentBy should match correctly" do
 
  it "should work for shoulds" do  
    @init = 0
    lambda { @init }.should be_different_by(1) { @init += 1 }
  end
  
  it "should work for should_nots" do
    @init = 0
    lambda { @init }.should_not be_different_by(2) { @init += 1 }
  end
end
For more info on creating custom matchers check out Jordan McKible's tutorial.

1 Response to “A handy assert_difference for rspec”

  1. Larry Diehl Says:
    RSpec has the "change" matcher that can do this too: http://rspec.rubyforge.org/rdoc/classes/Spec/Matchers.html#M000256

Leave a Reply