I’ve been refactoring the specs for my VCR gem[1] to take advantage of
the new shared example group parameterization. VCR supports both
FakeWeb and WebMock, with an appropriate adapter class implemented for
each. The adapter classes have nearly identical behavior, except for
the differences in the feature set of FakeWeb and WebMock. WebMock
supports a bunch of HTTP libraries that FakeWeb doesn’t, and also
supports matching requests on request headers and body. My specs
basically boil down to this:
describe VCR::HttpStubbingAdapters::FakeWeb do
it_should_behave_like ‘an http stubbing adapter’, [‘net/http’],
[:method, :uri, :host]
end
describe VCR::HttpStubbingAdapters::WebMock do
it_should_behave_like ‘an http stubbing adapter’, %w[net/http patron
httpclient em-http-request], [:method, :uri, :host, :body, :headers]
end
Basically, this is a shared example group that takes two parameters:
an array of supported HTTP libraries, and an array of supported
request match attributes. This design makes it easy to have a single
shared example group that specs out the full behavior of both
adapters. If I ever implement another adapter, it’s very easy to spec
it out this way, and pass arrays indicating the supported features.
This works great on ruby 1.8.7 and above, but I’m getting an error on
1.8.6. This gist[2] demonstrates the error in far simpler form. The
issue is due to the way I implemented module_eval_with_args on
1.8.6[3]. The lack of module_exec on 1.8.6 forced me to eval the
block twice: once using instance_eval_with_args (so that the block is
properly eval’d with arguments), and once with module_eval (so that we
can extract the instance method definitions, in order to deal with the
difference between module_eval and instance_eval). The error occurs
in the module_eval: because it’s not evaluated with any args, the
methods we call on the args raise NoMethodErrors.
Note that the “normal” use case of parameterized groups doesn’t have
this problem.
shared_example_group_for :foo do |arg|
it “can access the argument (#{arg}) in the doc
string” { arg.should … }
end
In this case, #it raises a no method error that is handled by our
anonymous class. Plus the only method being called on the arg is
#to_s, which is defined for every object. So this problem really only
exists when you call methods on arguments that are not defined on
Object, outside of the RSpec DSL methods.
So…how should we deal with this? A few ideas come to mind:
- Find a better way to fake module_exec on ruby 1.8.6. I’m not sure
if this is even doable. - Leave things as they are…but I don’t like this idea since the
error message is fairly cryptic. - Rescue the error and raise a more clear error message.
- Rescue the error and print a warning.
I lean towards #4, because the #module_eval is only necessary to
extract the instance method definitions. This error doesn’t prohibit
the shared example group from working on 1.8.6; as long as their are
no instance method definitions, or the instance method definitions
come before the error occurs, it can still work fine, I think. The
warning can hopefully make it clear that you just need to put the
instance method definitions first and everything will still work.
The one really difficult part about options #3 and #4 is the wording
of the waring/error: this is a fairly complex, nuanced error, and it’s
hard to boil it down into a sentence explaining the issue and how to
fix it.
Thoughts?
Myron
[1]
[2] rspec_example.rb · GitHub
[3]
http://github.com/rspec/rspec-core/blob/master/lib/rspec/core/extensions/module_eval_with_args.rb