Rspec, can you stub a method that doesn't exist on an object (or mock an object that can take any method)?

3k views Asked by At

This might sound like a terribly basic question... so let me explain the context. I'm retrieving an event from Stripe, but for the purposes of this, but this works for other things that I've considered.

The data that I need in the Stripe object is way buried, so I'd have to do event.data.object.date.lines.first.id. I'm not sure how to mock/stub this...

The only way I can think to do this is create a fake model that simulates a Stripe event object (in this example for me, keep in mind that Stripe events can't be user created). And then I can mock that fake model. But is that really the way to do it? Is there some way I can just use Rspec to stub the whole shebang and say that event.data.object.date.lines.first.id wherever it appears should just retun 1?

Oh and also... if not, and it turns out I do have to create a fake model... tips greatly appreciated, because right now I'm thinking this "Fake" model needs many layers of children, and is totally not worth the time investment.

2

There are 2 answers

6
Peter Alfvin On BEST ANSWER

RSpec provides no special mechanisms to access elements under test, so yes, you would need to somehow stub the id method and have it return whatever you wish (e.g. 1).

To do that, you must have a way to access the event object in your test so that you can stub it's data method. Once you can access to it, then you can use RSpec's receive_message_chain matcher to specify the sequence of methods for which, if called, you will return 1.

If there is some public Strip method you can mock to return a double for event, that would be the most straightforward approach.

If that's not possible, you could determine the class of event and use the allow_any_instance_of method to stub it's data method. The resulting mock would be as follows, assuming Foo was the class of the event object you're concerned about:

allow_any_instance_of(Foo)).to receive_message_chain(
  :data, :object, :date, :lines, :first, :id).and_return(1)

Here's a test that passes:

module Stripe
  class Event ; end
end
describe 'stack overflox example' do
  it 'should pass' do
    event = Stripe::Event.new
    expect(event).to receive_message_chain(:data, :object, :date, :lines, :first, :id).and_return(23)
    expect(event.data.object.date.lines.first.id).to eq(23)
  end
end
0
Gavin Miller On

In this given situation with Stripe, I'd choose mocking using a library instead of creating stubs with RSpec. I've used and would highly suggest stripe-ruby-mock.

The benefit of this approach is that you're mocking more closely to how Stripe is actually working, and you can switch to the actual stripe test api very easily.