[rspec-users] rspec macro style

David Chelimsky dchelimsky at gmail.com
Fri Aug 26 00:55:00 EDT 2011


On Aug 25, 2011, at 11:40 AM, Lenny Marks wrote:

> One of the things that always annoys me when I write/use typical macros in my specs is how the backtrace from failures don't include the caller of the macro. Not a huge deal because the example name can be used to track it down, but I lose the ability to do things like click on the line from the backtrace in my IDE(I use RubyMine). I find that feature to be a nice time saver. I've never heard any mention of this bothering anybody so maybe I'm just being pedantic but tweaking things so that the actual example implementation is yielded by the caller seems like a better style to me because it solves that problem. Just curious if anybody else has any thoughts one way or the other. I've been leaning toward the latter when possible.
> 
> Full example at:
> https://gist.github.com/1169172
> 
> ex. When the example below fails, line 17 is significant
> 
>     5  describe Email do
>     6    describe '.normalize', ' should strip email down to core address' do 
>     7      def self.normalized(original, options)
>     8        it %{normalizes '#{original}' as '#{options[:should_be]}'} do
>     9          Email.normalize(original).should == options[:should_be]
>    10        end
>    11      end
>    .....
>    16      describe 'it strips whitespace' do
>    17        normalized('   joe at somewhere.com', :should_be => 'joe at somewhere.com')
>    18      end
> 
> Failures:
> 
>  1) Email.normalize should strip email down to core address it strips whitespace normalizes '   joe at somewhere.com' as 'joe at somewhere.com'
>     Failure/Error: Email.normalize(original).should == options[:should_be]
>       expected: "joe at somewhere.com"
>            got: "   joe at somewhere.com" (using ==)
>     # org/jruby/RubyProc.java:268:in `call'
>     # ./spec/lib/email_spec.rb:9:in `normalized'
>     # org/jruby/RubyKernel.java:2028:in `instance_eval'

If you run this with the --backtrace flag, you should see line 17 as well.

> Compare to this where the significant lines are present in the backtrace
> 
> describe Email do
>     6    describe '.normalize', ' should strip email down to core address' do 
>     7      def self.normalized(original, &blk)
>     8        describe "'#{original}'" do
>     9          subject { Email.normalize(original) }
>    10          it { instance_eval(&blk) }
>    11        end
>    12      end
>    ......
>    17      describe 'it strips whitespace' do
>    18        normalized('   joe at somewhere.com') { should == 'joe at somewhere.com' }
>    19      end
> 
> Failures:
> 
>  1) Email.normalize should strip email down to core address it strips whitespace '   joe at somewhere.com' 
>     Failure/Error: normalized('   joe at somewhere.com') { should == 'joe at somewhere.com' }
>       expected: "joe at somewhere.com"
>            got: "   joe at somewhere.com" (using ==)
>     # org/jruby/RubyProc.java:268:in `call'
>     # ./spec/lib/email_spec.rb:18:in `(root)'
>     # org/jruby/RubyKernel.java:2028:in `instance_eval'
>     # ./spec/lib/email_spec.rb:10:in `normalized'
>     # org/jruby/RubyKernel.java:2028:in `instance_eval'

Interesting that that happens. I'm not clear on why. Regardless, this macro appraoch creates a lot of distance between the reader and the code that is being specified. Personally, I find this much easier to read:

describe Email do
  describe "#normalize" do
    it "strips whitespace" do
      Email.normalize('   joe at somewhere.com').should eq('joe at somewhere.com')
    end
  end
end

... and it would give exactly the spec output and failure message that I want.

Don't get me wrong - I'm all for sharing code where it provides a benefit that outweighs the loss of proximity, but I would typically do that with custom matchers, shared examples or simple helper methods (at the example level rather than the group level).

FWIW,
David





More information about the rspec-users mailing list