Search code examples
rubytestingrspec

How do I test the order of method calls in rspec?


I have a class that uses the command pattern to do a bunch of simple transformation steps in order. Data comes in as a data feed (in XML) and then is transformed through multiple steps using single-purpose step classes. So it might look like this (actual class names are different):

raw_data = Downloader.new(feed)
parsed_data = Parser.new(raw_data)
translated_data = Translator.new(parsed_data)
sifted_data = Sifter.new(translated_data)
collate_data = Collator.new(sifted_data)

etc.

I have unit tests for each class, and I have integration tests to verify the full flow, including that each class is called.

But I don't have any way to test the order they are called

I'd like some test so I can know: the Downloader is called first, then the Parser, then the Translator, etc.

This is in Ruby with Rspec 3.

I did find this: http://testpractices.blogspot.com/2008/07/ordered-method-testing-with-rspec.html but this is from 2008 and it's also really ugly. Is there a better way to test method execution order?

Thanks!


Solution

  • RSpec Mocks provides ordered since at least RSpec 3.0:

    You can use ordered to constrain the order of multiple message expectations. This is not generally recommended because in most situations the order doesn't matter and using ordered would make your spec brittle, but it's occasionally useful. When you use ordered, the example will only pass if the messages are received in the declared order.

    Note that RSpec agrees with @spickermann that this is not a recommended practice. However, there are some cases when it is necessary, especially when dealing with legacy code.

    Here is RSpec's passing example:

    RSpec.describe "Constraining order" do
      it "passes when the messages are received in declared order" do
        collaborator_1 = double("Collaborator 1")
        collaborator_2 = double("Collaborator 2")
    
        expect(collaborator_1).to receive(:step_1).ordered
        expect(collaborator_2).to receive(:step_2).ordered
        expect(collaborator_1).to receive(:step_3).ordered
    
        collaborator_1.step_1
        collaborator_2.step_2
        collaborator_1.step_3
      end
    end
    

    And failing examples:

    RSpec.describe "Constraining order" do
      it "fails when messages are received out of order on one collaborator" do
        collaborator_1 = double("Collaborator 1")
    
        expect(collaborator_1).to receive(:step_1).ordered
        expect(collaborator_1).to receive(:step_2).ordered
    
        collaborator_1.step_2
        collaborator_1.step_1
      end
    
      it "fails when messages are received out of order between collaborators" do
        collaborator_1 = double("Collaborator 1")
        collaborator_2 = double("Collaborator 2")
    
        expect(collaborator_1).to receive(:step_1).ordered
        expect(collaborator_2).to receive(:step_2).ordered
    
        collaborator_2.step_2
        collaborator_1.step_1
      end
    end