Pending Examples via NotImplementedError in rSpec
This post has been updated at 16/05/2013.
NotImplementedError can be sometimes really helpful. Unfortunately rSpec shows them as a failure, but aren't they just the exact equivalent of a pending message?
I realised it today, when I've been porting some parts of the AMQP gem on AMQ Client. There's a lot of raise NotImplementedError in AMQP currently and in specs, they all are displayed as failures, but it's not exactly true, it's rather a nonimplemented feature than a bug.
Fortunatelly it's easy to fix:
RSpec::Core::Example.send(:include, Module.new {
def self.included(base)
base.class_eval do
alias_method :__finish__, :finish
remove_method :finish
end
end
def finish(reporter)
if @exception.is_a?(NotImplementedError)
from = @exception.backtrace[0]
message = "#{@exception.message} (from #{from})"
@pending_declared_in_example = message
self.metadata[:pending] = true
@exception = nil
end
__finish__(reporter)
end
})
Easy, right? Now let's try it:
# encoding: utf-8
class HelloWorld
def from_london
"Hello World from London!"
end
def from_alasca
messgae = "Sorry, no network here, here are only bears!"
raise Errno::ECONNREFUSED.new(message)
end
def from_turkey
message = "Jeez, I have to learn Turkish first!"
raise NotImplementedError.new(message)
end
end
describe HelloWorld do
# This is supposed to work.
context "#from_london" do
it "should work" do
subject.from_london
end
end
# This is supposed to fail.
context "#from_alasca" do
it "should work" do
subject.from_alasca
end
end
# And this is supposed to be pending.
context "#from_turkey" do
it "should work" do
subject.from_turkey
end
end
end
Here's how does it look like:
Backtraces
Pending messages in rSpec don't support backtraces. Of course they don't, why would they need them? But when the NotImplementedError can be raised from any place in the code, it's getting pretty useful, so let's implement them:
module RSpec::Core
Example.send(:include, Module.new {
def self.included(base)
base.class_eval do
alias_method :__finish__, :finish
remove_method :finish
end
end
attr_reader :not_implemented
def not_implemented?
!! @not_implemented
end
def finish(reporter)
if @exception.is_a?(NotImplementedError)
@not_implemented = @exception
from = @exception.backtrace[0]
message = "#{@exception.message} (from #{from})"
self.metadata[:pending] = true
@pending_declared_in_example = message
@exception = nil
end
__finish__(reporter)
end
})
Formatters::BaseTextFormatter.send(:include, Module.new {
def self.included(base)
base.class_eval do
remove_method :dump_pending
remove_method :dump_backtrace
end
end
def dump_pending
return if examples.empty?
output.puts
output.puts "Pending:"
examples.each do |example|
pending_message = example.execution_result[:pending_message]
output.puts yellow(" #{example.full_description}")
output.puts grey(" # #{pending_message}")
output.puts grey(" # #{format_caller(example.location)}")
patterns = RSpec.configuration.backtrace_clean_patterns
if example.not_implemented? && patterns.empty?
dump_backtrace(example, example.not_implemented.backtrace)
end
end
end
def dump_backtrace(example,
backtrace = example.execution_result[:exception].backtrace)
format_backtrace(backtrace, example).each do |backtrace_info|
output.puts grey("#{long_padding}# #{backtrace_info}")
end
end
})
end
I know it's quite messy, but hey, it works! Just use the --backtrace option to get the full backtrace. Of course I'd be happy to contribute it to rSpec, I already asked David Chelimsky on Twitter if he'd be interested in such feature. Well ... enjoy it guys!
