RSpec sequence of returned values AND raised errors from stub
Asked Answered
C

4

10

I want to stub first two calls to HTTParty with raised exception and then, the third call, should return value.

   before do
          allow(HTTParty).to receive(:get).exactly(2).times.with(url).and_raise(HTTParty::Error)
          allow(HTTParty).to receive(:get).with(url).and_return('{}')
        end

but one allow override another one. how to set stub to raise errors for first few tries and then let it return a value?

Cutcliffe answered 3/6, 2016 at 8:4 Comment(2)
Take a look at WebMock, especially Multiple responses using chained to_return(), to_raise() or to_timeout declarationsRebuff
@Rebuff looks very reasonable! You can turn it into answer :)Cutcliffe
P
10

According to info provided in this github issue, you can also do that with the following pure-RSpec approach. It leverages the most general way of defining mock responses using block:

before do
  reponse_values = [:raise, :raise, '{}']
  allow(HTTParty).to receive(:get).exactly(3).times.with(url) do
    v = response_values.shift
    v == :raise ? raise(HTTParty::Error) : v
  end
end
Psychic answered 3/6, 2016 at 9:1 Comment(2)
Nice! If you set reponse_values = [nil, nil, '{}'] you can use the neat response_values.shift || raise(HTTParty::Error) trick from the GitHub issue.Rebuff
That's right! Although I tend to like "explicitness" a bit better in this case.Paratuberculosis
C
5

You can use an array of Proc instances to give you complete control:

my_object = double
values = [proc { raise ArgumentError }, proc { 2 }]

allow(my_object).to receive(:my_method).exactly(:twice) { values.shift.call }

expect { my_object.my_method }.to raise_error ArgumentError
expect(my_object.my_method).to eq 2

This way you can raise different exceptions across multiple calls or just return values to suit your needs.

You can use a similar approach to call the original when your stubbed responses have all been used:

allow(my_object).to receive(:my_method).and_wrap_original do |original, *args|
  values.empty? ? original.call(*args) : values.shift.call
end
Curse answered 28/8, 2020 at 12:34 Comment(0)
R
3

In this specific case, you could use WebMock:

Multiple responses using chained to_return(), to_raise() or to_timeout declarations.

Something like this should work:

before do
  stub_request(:get, url).
    to_raise(SomeException).then.
    to_raise(SomeException).then.
    to_return(body: '{}')
end

SomeException should be an actual network error.

Rebuff answered 3/6, 2016 at 8:47 Comment(0)
J
1

I like @b-f approach, but simplified it with #and_invoke, and for me, I've already specified the return values, so I just need to know they were both used.

allow(my_object).to receive(:my_method).and_invoke(proc { raise ArgumentError }, proc { 2 } )
expect(my_object).to have_received(:my_method).exactly(2).times
Jacquelinejacquelyn answered 1/1, 2023 at 16:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.