brewster brewster - 2 months ago 17
Ruby Question

How do i test rspec mocks with both ordered and x times?

I have a recursive method that i need to test the order of method calls, in addition to how many times they are called. the problem i have is, these methods calls alternate. for example..

it 'call methods in order' do
expect(@instance).to have_received(:foo).ordered
expect(@instance).to have_received(:bar).ordered
expect(@instance).to have_received(:foo).ordered
expect(@instance).to have_received(:bar).ordered
expect(@instance).to have_received(:foo).ordered
expect(@instance).to have_received(:bar).ordered
end


this fails because with

expected: 1 time with any arguments
received: 3 times with any arguments


foo
and
bar
being called 3 times is correct, but how do i test them alternating?

Answer

Something like this should work:

it 'call methods in order' do
  @method_calls = []

  allow(@instance).to receive(:foo) do
    @method_calls << :foo
  end

  allow(@instance).to receive(:bar) do
    @method_calls << :bar
  end

  ...

  expect(@method_calls).to eq([:foo, :bar, :foo, :bar, :foo, :bar])     
end

Here's a minimal test that I tried which demonstrates this fully:

require "rspec"

class MyClass
  def bar
    "bar"
  end

  def foo
    "foo"
  end

  def foobar
    foo
    bar
    foo
    bar
    foo
    bar
  end
end

describe "Foo" do
  describe "#foobar" do
    let(:instance) { MyClass.new }

    it "alternately calls foo/bar" do
      @method_calls = []

      allow(instance).to receive(:foo) do
        @method_calls << :foo
      end

      allow(instance).to receive(:bar) do
        @method_calls << :bar
      end

      instance.foobar

      expect(@method_calls).to eq([:foo, :bar, :foo, :bar, :foo, :bar])
    end
  end
end

Running this spec passes, but removing (say) one of the method calls in the foobar method results in this error:

F

Failures:

  1) Foo #foobar alternately calls foo/bar
     Failure/Error: expect(@method_calls).to eq([:foo, :bar, :foo, :bar, :foo, :bar])

       expected: [:foo, :bar, :foo, :bar, :foo, :bar]
            got: [:bar, :foo, :bar, :foo, :bar]

       (compared using ==)

       Diff:
       @@ -1,2 +1,2 @@
       -[:foo, :bar, :foo, :bar, :foo, :bar]
       +[:bar, :foo, :bar, :foo, :bar]
     # ./rspec_test.rb:38:in `block (3 levels) in <top (required)>'

Finished in 0.01517 seconds (files took 0.07714 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./rspec_test.rb:25 # Foo #foobar alternately calls foo/bar

Hope that makes sense.