Search code examples
ruby-on-railsrubyrspecrspec-rails

Rspec should_receive unordered checks


Currently if you have a method that is called multiple times and you want to ensure that a certain call occurred then you need to write a lot of dummy code.

events = []
Analytics.should_receive(:track) do |event|
  events << event
end.any_number_of_times

<<INVOKE CONTROLLER ACTION>>

event = events.find{|event| event.event_name == 'MyEventName'}
event.should_not be_nil
event.properties.should include({ property_a: value})

The above code is needed to 1. Record all the events that were tracked 2. Find at least one instance of a certain event name and match the properties

This results in a lot of lines of code when there should be a simpler way.

Analytics.should_receive(:track) do |event|
  expect(event.event_name).to eq('MyEventName')
  expect(event.properties).to include({ property_a: value})
end.at_least(:once)

Unfortunately the above will only work if the very first call to Analytics.track results in a match. If it was the second call this test will fail.

I have my own solution to try and make it generic and reusable but I would like to know if there is a simple way to accomplish this already that I may be missing.


Solution

  • You can try passing the exact event that you expect to with. The key here is stubbing track prior to calling should_receive.

    Here's an example code:

    require 'ostruct'
    
    class Analytics
      def self.run
        track(OpenStruct.new(event_name: 'test1', properties: { a: '1' }))
        track(OpenStruct.new(event_name: 'test2', properties: { a: '2' }))
      end
    
      def self.track(event)
      end
    end
    
    describe Analytics do
      it 'verifies the second event' do
        expected_event = OpenStruct.new(event_name: 'test2', properties: { a: '2' })
    
        Analytics.stub(:track)
        Analytics.should_receive(:track).with(expected_event)
    
        Analytics.run
      end
    end