Reputation: 28783
I have an instance method in a class like so:
def get(path)
try_request do
RestClient.get(path, headers)
end
end
and the try_request
method contains a retry
when the code is rescued, so that the block can be re-requested after doing something to rectify the Unauthorized exception:
def try_request
tries ||= 2
EMS::Response.new(yield)
rescue RestClient::Unauthorized => e
tries -= 1
if tries.positive?
ems_credentials.update_and_return_token
retry
end
Rails.logger.error "Exception on #{e.class}: #{e.message}"
EMS::Response.new(unauthorized_response)
end
So I'm trying to test this retry happens with the following test:
it 'retries and returns a successful response' do
# allow the RestClient to raise an Unauthorized response
allow(RestClient).to receive(:get).and_raise(RestClient::Unauthorized)
# call the endpoint which would cause the above error
described_class.new.get('/endpoint')
# mock out a successful response:
rest_response = double('rest_response', code: 200, body: { test: 'Test' }.to_json)
# update the RestClient to return the successful response this time
allow(RestClient).to receive(:get).and_return(rest_response)
# retry the request and check the response is as expected:
client_response = described_class.new.get('/endpoint')
expect(client_response.code).to eq(200)
expect(client_response.success?).to be_truthy
expect(client_response.body[:test]).to eq('Test')
expect(client_response).to be_an_instance_of(EMS::Response)
end
Which passes and gives me full code coverage of that rescue.
However what is actually happening is that because the request is raising the error, we end at this point straight away: EMS::Response.new(unauthorized_response)
and because I'm NOT doing anything with the expect
s at this point in the test it's then continuing with the test (so considers all of that rescue code called) and then we are re-mocking out the response and expects so we end up with a test that thinks the entire block is fully tested code... but we don't actually verify that if the request is made... it successfully calls the same method again if the exception has been raised the first time, and THEN responds successfully or fails if the retry happens more than once.
How can I properly test these three scenarios?
1.) Test that the request retries once more if exception is raised.
2.) Test that if the second attempt passes I get a successful response.
3.) Test that if the second attempt fails, then I get that final EMS::Response.new(unauthorized_response)
response.
Hopefully that makes sense? I looked into using should_receive(:retry)
but I couldn't see how I would use this to verify that the actual same code is being re-called and how I could verify it only happens once.
Upvotes: 0
Views: 1973
Reputation: 6628
I'd try ordered
You should be able to do this
expect(RestClient).to receive(:get).ordered.and_raise(RestClient::Unauthorized)
expect(RestClient).to receive(:get).ordered.and_return(rest_response)
# ...
expect(client_response.code).to eq(200)
Next example
expect(RestClient).to receive(:get).ordered.and_raise(RestClient::Unauthorized)
expect(RestClient).to receive(:get).ordered.and_raise(RestClient::Unauthorized)
expect(RestClient).to receive(:get).ordered.and_return(rest_response)
# ...
expect(client_response.code).to eq(200)
And lastly
expect(RestClient).to receive(:get).ordered.and_raise(RestClient::Unauthorized)
# check that it was unsuccesfull
(You can use allow
instead of expect
if you want, I just think expect
is more fitting)
If that doesn't work (I could not test it right away), you can always use the block version of double expectations (but this will be a bit more ugly)
errors_to_raise = 2
allow(RestClient).to receive(:get) do
return rest_response if errors_to_raise <= 0
errors_to_raise -= 1
raise RestClient::Unauthorized
end
# ...
expect(client_response.code).to eq(200)
Upvotes: 2