Search code examples
ruby-on-railsrubyrspecrest-client

Testing RestClient::Request.execute in RSpec


I am trying to test that 'ApiClient.do_request' is sending the correct arguments and calling 'RestClient::Request.execute.' However, my below specs aren't working as intended. RSpec is unable to properly call 'RestClient::Request.execute,' as the method's 'response' variable keeps coming back as nil, which is why I get the below TypeError when JSON.parse(response) is called. What's strange to me is that I have no issues when walking through the process in Rails console. I'm sure there's something obvious I'm missing here... Any ideas why this is happening, or perhaps suggestions on how I can test this more effectively?

Model:

class ApiClient < ActiveRecord::Base

  BASE_PATH = "http://api.bandsintown.com/artists/"
  APP_ID = ENV["APP_ID"]

  def do_request(method:, base_url:BASE_PATH, app_id:APP_ID, url:, format:"json", options: nil)
    response = RestClient::Request.execute(method: method.to_sym, 
                                           url: "#{base_url}#{url}.#{format}?api_version=2.0&app_id=#{app_id}#{options}", 
                                           timeout: 10)

    JSON.parse(response)
  end

Spec:

describe ApiClient do

  describe "do_request" do

    context "when all required arguments are present" do
      it "RestClient executes the request" do
        @test_client = ApiClient.new

        expect(RestClient::Request).to receive(:execute).with(:method=>:get, :url=> "http://api.bandsintown.com/artists/Damien%20Jurado/events/search.json?api_version=2.0&app_id=ShowBoatTest&location=San+Diego,CA&radius=15", :timeout=>10)

        @test_client.do_request(method:"get", app_id:"ShowBoatTest",url:"Damien%20Jurado/events/search",options:"&location=San+Diego,CA&radius=15")
      end
    end
  end

RSpec Error:

  1) ApiClient do_request when all required arguments are present RestClient executes the request
 Failure/Error: JSON.parse(response)

 TypeError:
   no implicit conversion of nil into String
 # /Users/slamflipstrom/.rvm/gems/ruby-2.1.5/gems/json-1.8.3/lib/json/common.rb:155:in `initialize'
 # /Users/slamflipstrom/.rvm/gems/ruby-2.1.5/gems/json-1.8.3/lib/json/common.rb:155:in `new'
 # /Users/slamflipstrom/.rvm/gems/ruby-2.1.5/gems/json-1.8.3/lib/json/common.rb:155:in `parse'
 # ./app/models/api_client.rb:12:in `do_request'
 # ./spec/models/api_client_spec.rb:13:in `block (4 levels) in <top (required)>'

Solution

  • Not considering minor syntax errors like api_version=2.0&app_id=ShowBoat in your ApiClient class, the problem seems to be caused by your RestClient::Request.execute request returning nil.

    Debug or inspect the value of response and make sure it is not nil.

    class ApiClient < ActiveRecord::Base
    
      BASE_PATH = "http://api.bandsintown.com/artists/"
      APP_ID = ENV["APP_ID"]
    
      def do_request(method:, base_url:BASE_PATH, app_id:APP_ID, url:, format:"json", options: nil)
        response = RestClient::Request.execute(method: method.to_sym, 
                                               url: "#{base_url}#{url}.#{format}?api_version=2.0&app_id=#{app_id}#{options}", 
                                               timeout: 10)
        puts response.inspect
        response ? JSON.parse(response) : []
      end
    

    On the other hand, you are testing ApiClient class's implementation. In other words, you're testing that the class is internally calling some other class in specific manner. If you decide to replace the RestClient with HTTParty for example at some later time, your tests will fail (even though they shouldn't, because your class is still working correctly).

    So, testing the class's implementation makes your tests "fragile". Instead, give an input and test for the expected correct output.

    describe ApiClient do
      context "with all required arguments" do
        describe "#do_request" do
          it "returns object in correct format" do
            response = subject.do_request(method:"get", app_id:"ShowBoatTest",url:"Damien%20Jurado/events/search",options:"&location=San+Diego,CA&radius=15")
            expect(response.body).to include(title: "Damien Jurado @ The Casbah in San Diego, CA")
            # etc.
          end
        end
      end
    end