RSpec: Have xit Complain When Example Is Fixed

RSpec helps us to test our code. As you might already know, examples may be declared pending (most probably because a feature can be described even if not yet implemented).

As a different use case, when you intentionally break things, you can use xit to temporarily declare an existing example pending. And, RSpec can notify you when an example you've declared pending gets (un-)intentionally fixed. This can be accomplished by wrapping misbehaving code into a pending block.

Unfortunately, you cannot have both features together - the one-character pending declaration and the FIXED notification. But you can at least try. The following monkey patch replaces RSpec's xit with an incarnation that tells you when pending examples pass. It does not look quite as simple as you could have expected:

class << RSpec::Core::ExampleGroup
  # replaces original xit with a pending fixed complaining one
  def xit(desc=nil, *args, &block)
    it(desc, *args) do
      example.metadata[:caller].delete_if {|line| line.include?("in `xit'")}
      pending("not yet green test") {self.instance_exec(&block)}
    end
  end
end

Things you might not have expected: self.instance_exec is required for matchers to be available to your example. Dive into RSpec's code to get to know why ...

The example.metadata[:caller] is modified for the purpose of displaying the correct location of your example code. Without this modification, RSpec would display the line containing it within the definition of xit above. For unknown reasons, you'll nevertheless get that line sometimes when the example is displayed as pending. Not always, just sometimes, even though deterministic.

Additionally, there is even a self-test to notify you when this monkey patch is broken (or, more probably, has been removed by someone who considered it superfluous). It does not quite accomplish a good code / test code ratio yet:

module RSpec; module Core
describe Example, "xit" do
  let(:example_group) { ExampleGroup.describe("example group") }
  def example_failed(example)
    @failed_examples << example.description
  end
  let(:reporter) { Reporter.new.tap {|r| r.register_listener self, :example_failed } }
  before do
    @failed_examples = []
  end
  describe "with fixed example" do
    before do
      example_group.xit "fixed it" do
        nil.should be_nil
      end
    end
    it "should fail" do
      example_group.run reporter
      @failed_examples.should include('fixed it')
    end
  end
  describe "with not fixed example" do
    before do
      example_group.xit "not fixed yet" do
        nil.should_not be_nil
      end
    end
    it "should not fail" do
      example_group.run reporter
      @failed_examples.should_not include("not fixed yet")
    end
  end
end
end; end

Have fun with RSpec!

More great blog posts from Kai-Uwe