Lynn
Lynn

Reputation: 1123

Rspec - How to write specs for a chain of methods

I'm learning rspec, and I'm wondering what the most effective way to write specs for a method that calls a chain of other methods. For example:

class Example1
  def foo(dependency)
     dependency.bar("A")
     dependency.baz("B")
     dependency.bzz("C")
  end
end

Ideally I would like to write specs like this:

it "should call bar" do
   ex = Example1.new
   dep = mock
   dep.should_receive(:bar).with("A")
   ex.foo(dep)
end
it "should call baz"
   ... 
it "should call bzz"
   ...

When I do that, however, I (understandably) get exceptions like 'unexpected method call baz'.

So what's the best way to deal with that? I have come up with a couple of ideas but I don't know if any of them are good.

  1. Make the mock dependency an "as_null_object" so it ignores the extra calls. (Down side - if I was calling unwanted random stuff on that object, I wouldn't know it)
  2. Stub out the two unused dependency method calls in each spec (Down side - feels very DRY)
  3. Stub out all three dependency calls in a 'before' (Down side - puts a lot of junk in the 'before')

Upvotes: 0

Views: 918

Answers (3)

Steve
Steve

Reputation: 15736

It sounds like you have already worked out which options RSpec gives you. I would go with option 1 and use as_null_object. It's true that you might be missing other random method calls on that object but I would be ok with that if the point of each of these tests was simply to assert that a particular method was being called, especially if I have higher level integration tests covering this method.

If you really need to verify that no other methods are called on dependency then option 3 may make sense but such tests can be brittle when implementation changes.

As an aside, to make your test a little simpler you can use subject to avoid explicitly instantiating Example1 (assuming you are using a describe Example1 block), e.g.:

subject.foo(dep)

(However see discussion in comments - an implicit subject can hide intention).

Upvotes: 1

megas
megas

Reputation: 21791

I would test this code in this way:

describe "checking foo method" do

  before(:each) do
    @ex = Example1.new
    @test = ClassOfDependency.any_instance
    @test.as_null_object
  end

  after(:each) do
    @ex.foo(dependency)
  end

  it "should call bar method" do
    @test.should_receive(:bar).with("A")
  end

  it "should call baz method" do
    @test.should_receive(:baz).with("B")
  end

  it "should call bzz method" do
    @test.should_receive(:bzz).with("C")
  end
end

But I'm not sure that it will work, hope it'll give you some ideas.

Upvotes: 1

Andy Waite
Andy Waite

Reputation: 11076

RSpec has a feature called stub_chain: https://www.relishapp.com/rspec/rspec-mocks/v/2-0/docs/stubs/stub-a-chain-of-methods

What about testing them all in one example?

it "should call bar"
   ex = Example1.new
   dep = mock
   dep.should_receive("bar").with("A")
   dep.should_receive("baz").with("B")
   dep.should_receive("bzz").with("C")
   ex.foo(dep)
end

I believe you can use RSpec to verify the order in which they are called, if that matters.

However, this kind of approach often indicate that there is a problem with how the code is written, e.g. a Law Of Demeter violation. In your example, foo should be a methed on the dependency's class.

Upvotes: 1

Related Questions