Working Remotely? Thinking about it?

Are you a member of a geographically dispersed team? Are you thinking of working remotely, or hiring remote developers? Wide Teams is my new blog and podcast for distributed teams. Check it out for getting started guides, tips and best practices, news, interviews, screencasts, and more all about working remotely and collaborating with wide-spread teams.

Complex Hash Expectations in RSpec

2008 October 28
by avdi

When spec-ing something that calls method which takes a set of nested hashes (as many Rails methods do), it may be tempting to use “#hash_including”:http://blog.davidchelimsky.net/2008/5/27/rspec-1-1-4 to test for only the values you care about. However @#hash_including@ won’t work the way we might hope for nested hashes. Take the following (highly contrived) example:

describe CoffeeMaker do
  before :each do
    @it = CoffeeMaker.new
  end

  it "should receive #make_coffee with roast => medium" do
    @it.should_receive(:make_coffee).
      with(:water => :filtered,
           :beans => hash_including(:roast => :medium))

    @it.make_coffee(:water => :filtered,
                    :beans => {
                      :o rigin => "Guatemala",
                      :roast  => :medium
                    })
  end
end

If we run this we get a failure:


1)
Spec::Mocks::MockExpectationError in 'CoffeeMaker should receive #make_coffee with roast => medium'
Mock 'CoffeeMaker' expected :make_coffee with ({:water=>:filtered, :beans=>#:dark}>}) but received it with ({:water=>:filtered, :beans=>{:roast=>:medium, :o rigin=>"Guatemala"}})

Clearly @#hash_including@ was only intended to work with shallow hashes.

Instead, we can use a lesser-known feature of RSpec’s mock objects to test only the values we care about:

describe CoffeeMaker do
  before :each do
    @it = CoffeeMaker.new
  end

  it "should receive #make_coffee with roast => medium" do
    @it.should_receive(:make_coffee) do |options|
      options[:beans][:roast].should == :medium
    end

    @it.make_coffee(:water => :filtered,
                    :beans => {
                      :o rigin => "Guatemala",
                      :roast  => :medium
                    })
  end
end

Here we’ve supplied a block to @#should_receive@. The block will be called when the mocked method is called, and will be passed whatever arguments the mocked method was called with. Inside we can use any kind of RSpec assertions we like.

Here’s the failure message if we supply @:roast => :dark@ instead of @:medium@:


Spec::Mocks::MockExpectationError in 'CoffeeMaker should receive #make_coffee with roast => medium'
Mock 'CoffeeMaker' received :make_coffee but passed block failed with: expected: :medium,
got: :dark (using ==)

Bookmark and Share
Creative Commons License
This work, unless otherwise expressly stated, is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License.

Related posts:

  1. Hash Transforms in Ruby
  2. Getting Pedantic About Ruby Semantics
  3. Sustainable Development in Ruby, Part 2: Method Injection
  4. Class.new and .inherited()
blog comments powered by Disqus