Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test HTTParty API call with Ruby and RSpec

I am using the HTTParty gem to make a call to the GitHub API to access a list of user's repos.

It is a very simple application using Sinatra that displays a user's favourite programming language based on the most common language that appears in their repos.

I am a bit stuck on how I can write an RSpec expectation that mocks out the actual API call and instead just checks that json data is being returned.

I have a mock .json file but not sure how to use it in my test.

Any ideas?

github_api.rb

require 'httparty'

class GithubApi
  attr_reader :username, :data, :languages

  def initialize(username)
    @username = username
    @response = HTTParty.get("https://api.github.com/users/#{@username}/repos")
    @data = JSON.parse(@response.body)
  end
end

github_api_spec.rb

require './app/models/github_api'
require 'spec_helper'

describe GithubApi do
  let(:github_api) { GithubApi.new('mock_user') }

  it "receives a json response" do

  end
end

Rest of the files for clarity:

results.rb

require 'httparty'
require_relative 'github_api'

class Results

 def initialize(github_api = Github.new(username))
   @github_api = github_api
   @languages = []
 end

 def get_languages
   @github_api.data.each do |repo|
     @languages << repo["language"]
   end
 end

 def favourite_language
   get_languages
   @languages.group_by(&:itself).values.max_by(&:size).first
 end
end

application_controller.rb

require './config/environment'
require 'sinatra/base'
require './app/models/github_api'

class ApplicationController < Sinatra::Base

  configure do
    enable :sessions
    set :session_secret, "@3x!ilt£"
    set :views, 'app/views'
  end

  get "/" do
    erb :index
  end

  post "/user" do
    @github = GithubApi.new(params[:username])
    @results = Results.new(@github)
    @language = @results.favourite_language
    session[:language] = @language
    session[:username] = params[:username]
    redirect '/results'
  end

  get "/results" do
    @language = session[:language]
    @username = session[:username]
    erb :results
  end

run! if app_file == $0

end
like image 239
dwalsh91 Avatar asked Dec 06 '18 15:12

dwalsh91


2 Answers

There are multiple ways you could approach this problem.

You could, as @anil suggested, use a library like webmock to mock the underlying HTTP call. You could also do something similar with VCR (https://github.com/vcr/vcr) which records the results of an actual call to the HTTP endpoint and plays back that response on subsequent requests.

But, given your question, I don't see why you couldn't just use an Rspec double. I'll show you how below. But, first, it would be a bit easier to test the code if it were not all in the constructor.

github_api.rb

require 'httparty'

class GithubApi
  attr_reader :username

  def initialize(username)
    @username = username
  end

  def favorite_language
    # method to calculate which language is used most by username
  end

  def languages
    # method to grab languages from repos
  end

  def repos
    repos ||= do
      response = HTTParty.get("https://api.github.com/users/#{username}/repos")
      JSON.parse(response.body)
    end
  end
end

Note that you do not need to reference the @username variable in the url because you have an attr_reader.

github_api_spec.rb

require './app/models/github_api'
require 'spec_helper'

describe GithubApi do
  subject(:api) { described_class.new(username) }

  let(:username) { 'username' }

  describe '#repos' do
    let(:github_url) { "https://api.github.com/users/#{username}/repos" }
    let(:github_response) { instance_double(HTTParty::Response, body: github_response_body) }
    let(:github_response_body) { 'response_body' }

    before do
      allow(HTTParty).to receive(:get).and_return(github_response)
      allow(JSON).to receive(:parse)

      api.repos
    end

    it 'fetches the repos from Github api' do
      expect(HTTParty).to have_received(:get).with(github_url)
    end

    it 'parses the Github response' do
      expect(JSON).to have_received(:parse).with(github_response_body)
    end
  end
end

Note that there is no need to actually load or parse any real JSON. What we're testing here is that we made the correct HTTP call and that we called JSON.parse on the response. Once you start testing the languages method you'd need to actually load and parse your test file, like this:

let(:parsed_response) { JSON.parse(File.read('path/to/test/file.json')) }
like image 72
aridlehoover Avatar answered Nov 19 '22 02:11

aridlehoover


You can mock those API calls using https://github.com/bblimke/webmock and send back mock.json using webmock. This post, https://robots.thoughtbot.com/how-to-stub-external-services-in-tests walks you through the setup of webmock with RSpec (the tests in the post mock GitHub API call too)

like image 39
Anil Cherukuri Avatar answered Nov 19 '22 01:11

Anil Cherukuri