I have a Rspec test which needs to double the Request
and HTTPI
class/module and return a mocked REST response. I have this working, until this method makes another REST call and needs to return a new REST response.
API Class
NOTE this is a trimmed down version of the class but the gist is there
class API
include HTTPI
def find_device(id)
req = create_request('path')
req.body = { :customerId => MultiJson.dump(customer_id) }
return call(req)
end
def find_other_device(other_id)
req = create_request('path')
req.body = { :other_id => MultiJson.dump(other_id) }
data = call(req)
return data
end
def call(req)
response = HTTPI.send(req)
return response.body
end
end
Device file calling REST method
class Device
@api = API.new(:open_timeout => 30, :read_timeout => 30)
def get_device
devices = @api.find_device(@id)
log.info("First Call Made")
other_call = @api.find_other_device(@other_id)
end
end
spec file
Rspec.describe Device do
resp = {code: 200, body: resp_body, raw_body: "TEST BODY", cookies: [cookie_list_hash]}
resp2 = {code: 200, body: resp_body_2, raw_body: "TEST BODY 2", cookies: [cookie_list_hash]}
let!(:request) {class_double('Request', new: http).as_stubbed_const} # I understand this causes the HTTPI send request to always return the same resp, but without it the test does not even get past the first call
let!(:http) {class_double('HTTPI', send: resp).as_stubbed_const}
it 'test' do
Device.get_device
end
end
The hope is to make a double that returns the resp var first and on the second call the :send, it returns resp2.
I am rather new to ruby also, so this may be pretty ugly.
I will focus on your spec, though there are some other things in your other classes that may need review (depending on what you want to achieve).
Maybe if you write it another way you can get the logic behind it.
First of all, you need to define the responses as let
s as well; also, you can take a look at returning different values across multiple calls.
Rspec.describe Device do
let(:resp) do
{
code: 200, body: resp_body, raw_body: "TEST BODY", cookies: [cookie_list_hash]
}
end
let(:resp2) do
{
code: 200, body: resp_body_2, raw_body: "TEST BODY 2", cookies: [cookie_list_hash]
}
end
let!(:request) { class_double('Request', new: http).as_stubbed_const }
let!(:http) { class_double('HTTPI').as_stubbed_const }
before do
# see https://www.rubydoc.info/github/rspec/rspec-mocks/RSpec%2FMocks%2FMessageExpectation:and_return
allow(http).to receive(:send).and_return(resp, resp2)
end
it 'test' do
Device.get_device
end
end
Having said that, which may resolve the problem with your spec, it seems that you also may want your api
object to be an instance variable, and not something defined in your class:
class Device
def api
# This will create the API object only once, and return it each time you call it in #get_device
@api ||= API.new(:open_timeout => 30, :read_timeout => 30)
end
def get_device
devices = api.find_device(@id)
log.info("First Call Made")
other_call = api.find_other_device(@other_id)
end
end
But, again, this depends on what you want to achieve and if the code you pasted is complete/correct or not, so sorry if this doesn't apply.