ruby-on-railsrubyrspecnet-http

ruby (rails) Net::HTTPResponse undefined method closed?


Ruby-on-Rails, Net::HTTPResponse NoMethodError:undefined method `closed?' for nil:NilClass

Hello, I have faced an issue: I use the RSpec framework and I need to mock a response within a method which I test.

here is a sample of the method:

def perform_request
    response = DataClient.new.request(arg: 'some_arg')
    # do some thing with the response
end 

RSpec file:
    describe '#perform_request'
      let(:response) do
        Net::HTTPResponse.new(1.0, 200, "OK")
      end

      let(:double_response) {double('DataClient', request: response)}

      before do
       response.body = {some_key:some_value}.to_json
       allow(DataClient).to receive(:new).and_return(double_response)   
      end

     it 'returns a response body' do
       MyClass.new.perform_request
       expect(response.body).to eq({some_key:some_value}.to_json)
     end 
   end

the problem happens when I try to call the 'body' method for the Net::HTTPResponse instance to read the body of it.

It throws the NoMethodError with the message "undefined method `closed?' for nil:NilClass" which I have no idea about.

I have tried to search for any explanation with question variations such as 'Net::HTTPResponse body undefined method closed?'', 'Ruby Net:: HTTPResponse body undefined methodclosed?', 'Ruby undefined method `closed?' but couldn't find any relevant answer.

In advance, I have to say I can't use any other client library except the Net:: HTTP without any wrappers. Please don't advice it.

The point of the question is not just to get an explanation but also to get advice how to solve the problem. Thank you in advance for any explanation and advice.


Solution

  • The error is thrown because the instance of Net::HTTPResponse you created in your test setup is trying to verify if the connection from which it expects to get data is closed.

    I would not bother with creating an actual instance of Net::HTTPRequest in your test setup. All your class knows is that DataClient.new.request returns an object that responds to .body. I would change your setup as follows:

    let(:response){ instance_double(Net::HTTPResponse, body: response_body)}
    
    let(:response_body) { { key: :value }.to_json }
    

    Using an instance double instead of a real instance allows you to not have to worry about the actual implementation details of the class, while still getting the assurance that you are calling methods the class supports.