Rspec macro style

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:

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(’ [email protected]’, :should_be =>
[email protected]’)
18 end

Failures:

  1. Email.normalize should strip email down to core address it strips
    whitespace normalizes ’ [email protected]’ as ‘[email protected]
    Failure/Error: Email.normalize(original).should ==
    options[:should_be]
    expected: “[email protected]
    got: " [email protected]" (using ==)

    org/jruby/RubyProc.java:268:in `call’

    ./spec/lib/email_spec.rb:9:in `normalized’

    org/jruby/RubyKernel.java:2028:in `instance_eval’

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(’ [email protected]’) { should ==
[email protected]’ }
19 end

Failures:

  1. Email.normalize should strip email down to core address it strips
    whitespace ’ [email protected]
    Failure/Error: normalized(’ [email protected]’) { should ==
    [email protected]’ }
    expected: “[email protected]
    got: " [email protected]" (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’

-lenny

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

8        it %{normalizes '#{original}' as '#{options[:should_be]}'} do
  1. Email.normalize should strip email down to core address it strips whitespace
    normalizes ’ [email protected]’ as ‘[email protected]
    Failure/Error: Email.normalize(original).should == options[:should_be]
    expected: “[email protected]
    got: " [email protected]" (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.


# 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:innormalized’
# 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(’ [email protected]’).should
eq(‘[email protected]’)
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