SameShirtEveryDay.com

Personal blog of the one called Alex Gorbatchev, from Toronto, Canada.

Improve your RSpec with simple custom matchers

Posted on September 15th, 2007 by Alex Gorbatchev. In Ruby, Testing. No comments yet...

RSpec is great:

it "should be its own root" do
  @node.root.should == @node
end

it "should add a child" do
  @node.children.size.should == 0

  child = @klass.new

  @node.add_child(child)
  @node.children.size.should == 1

  child.parent.should == @node
  child.root.should == @node
end

Looks beautiful. However, look at line #2. What would the output be if that test fails? The message would be something like this: "expected #{@target.inspect} to the same as #{@expected.inspect}".

Inspecting an object like a tree node could result in multiple pages worth of data and the output basically becomes unreadable if there’s an array of objects.

This problem could easily be fixed with a custom expectation matcher.

module BeTheSameAsMatcher
  class BeTheSameAs
    def initialize(expected)
      @expected = expected
    end

    def matches?(target)
      @target = target
      @target.eql?(@expected)
    end

    def failure_message
      "expected <#{to_string(@target)}> to " +
      "the same as <#{to_string(@expected)}>"
    end

    def negative_failure_message
      "expected <#{to_string(@target)}> not to " +
      "be the same as <#{to_string(@expected)}>"
    end

    # Returns string representation of an object.
    def to_string(value)
      # indicate a nil
      if value.nil?
        'nil'
      end

      # join arrays
      if value.class == Array
        return value.join(", ")
      end

      # otherwise return to_s() instead of inspect()
      return value.to_s
    end
  end

  # Actual matcher that is exposed.
  def be_the_same_as(expected)
    BeTheSameAs.new(expected)
  end
end

As you can see, methods failure_message and negative_failure_message define our error messages. Instead of default inspect call, I’m using a custom to_string method which will either return a join for an Array or to_s for any other object.

To make this available in all of your specs, the module needs to be added in your `spec_helper.rb` like so:

require 'spec/be_the_same_as'

Spec::Runner.configure do |config|
  config.include(BeTheSameAsMatcher)
end

After this, we can change our line #2 from the original script to this:

it "should be its own root" do
  @node.root.should be_the_same_as(@node)
end
You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

No comments yet, be the first one!

Leave a Reply

Allowed tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> , rel="nofollow" in use - no link dropping, no keywords or domains as names; do not spam, and do not advertise!

home
Subscribe to this blog Follow me on Twitter My bookmarks on Delicious My photography on Flickr