Skip to content

Instantly share code, notes, and snippets.

@yedhink
Created October 3, 2022 06:20
Show Gist options
  • Save yedhink/911578e30be70e54bbc1f419ab08f0ac to your computer and use it in GitHub Desktop.
Save yedhink/911578e30be70e54bbc1f419ab08f0ac to your computer and use it in GitHub Desktop.
Mocking 3rd party APIs

Let's say that in our application, we need to fetch events from our Google Calendar. So if we want to write test cases verifying this logic, then we ought to mock the API calls to Google APIs.

We can use Webmock to mock these requests. But let's see how we can do this in a DRY fashion.

First let's start by creating a support file at test/support/google_calendar_api_support.rb and add the following content to it:

# frozen_string_literal: true

module GoogleCalendarApiSupport
  JSON_CONTENT_TYPE = {'Content-Type'=>'application/json'}

  def default_calendar
    GoogleCalendarApi::BaseService::CALENDAR_ID
  end

  def calendar_events_url
    "https://www.googleapis.com/calendar/v3/calendars/#{default_calendar}/events"
  end

  def stub_gcal_api_request(body, status = 200, method = :post)
    any_query_params = hash_including({})
    stub_request(method, calendar_events_url).with(query: any_query_params)
      .to_return(body: body.to_json,
        status: status,
        headers: JSON_CONTENT_TYPE
      )
  end

  def mock_gcal_events_listing(user, item_count: 1, uniq_event_id: true)
    event_items = []
    item_count.times do |i|
      event_id = uniq_event_id ? "#{user.id}_event_id_#{i}" : "event_id"
      event_items << build_calendar_events_list_items_response_hash(user, event_id: event_id)
    end
    body = gcal_calendar_events_list_response(user, items: event_items)
    stub_gcal_api_request(body, 200, :get)
    body
  end

  def gcal_calendar_events_list_response(user, now: Time.current.in_time_zone('Kolkata'), items:)
    {
      "accessRole": "owner",
      "defaultReminders": [
        {
          "method": "popup",
          "minutes": 1440
        },
        {
          "method": "popup",
          "minutes": 10
        }
      ],
      "etag": "\"etag\"",
      "items": items,
      "kind": "calendar#events",
      "nextPageToken": "",
      "summary": user.email,
      "timeZone": "Asia/Kolkata",
      "updated": now
    }
  end

  def build_calendar_events_list_items_response_hash(user, now: Time.current.in_time_zone('Kolkata'), event_id:)
    {
      "attendees": [
        {
          "email": user.email,
          "organizer": true,
          "responseStatus": "accepted",
          "self": true
        },
        {
          "email": "oliver@example.com",
          "responseStatus": "accepted"
        },
        {
          "email": "eve@example.com",
          "responseStatus": "accepted"
        },
        {
          "email": "john@example.com",
          "responseStatus": "needsAction"
        }
      ],
      "conferenceData": {
        "conferenceId": "tdd-bpbm-vgp",
        "conferenceSolution": {
          "iconUri": "https://fonts.gstatic.com/s/i/productlogos/meet_2020q4/v6/web-512dp/logo_meet_2020q4_color_2x_web_512dp.png",
          "key": {
            "type": "hangoutsMeet"
          },
          "name": "Google Meet"
        },
        "entryPoints": [
          {
            "entryPointType": "video",
            "label": "random_url.com",
            "uri": "https://random_url.com"
          },
          {
            "entryPointType": "more",
            "pin": "123123",
            "uri": "https://random_url"
          },
          {
            "entryPointType": "phone",
            "label": "+1 209-850-2316",
            "pin": "921931",
            "regionCode": "US",
            "uri": "tel:+1-209-850-2316"
          }
        ],
        "signature": "12319sdasadsncn"
      },
      "created": now,
      "creator": {
        "email": user.email,
        "self": true
      },
      "end": {
        "dateTime": now + 1.hour,
        "timeZone": "Asia/Kolkata"
      },
      "etag": "\"etag\"",
      "guestsCanInviteOthers": false,
      "hangoutLink": "https://random_url.com",
      "htmlLink": "https://www.somegooglecalendarlink.com",
      "iCalUID": "icalUid1@google.com",
      "id": event_id,
      "kind": "calendar#event",
      "organizer": {
        "email": user.email,
        "self": true
      },
      "originalStartTime": {
        "dateTime": now,
        "timeZone": "Asia/Kolkata"
      },
      "recurringEventId": "re_id",
      "reminders": {
        "useDefault": true
      },
      "sequence": 0,
      "start": {
        "dateTime": now,
        "timeZone": "Asia/Kolkata"
      },
      "status": "confirmed",
      "summary": "daily meeting",
      "updated": now
    }
  end
end

These support files ought to be included in our test class after requiring them. For example, refer: https://www.bigbinary.com/books/learn-rubyonrails-book/loading-behavior-of-ruby-on-rails-in-depth#including-files-outside-of-the-app-directory

Now let's create a test case verifying the event fetching service is working as intended, like so:

require "test_helper"

class GoogleCalendarApi::EventFetchServiceTest < ActiveSupport::TestCase
  def setup
    @user = create(:user)
    create(:integration, user: @user)

    @event_fetch_service = GoogleCalendarApi::EventFetchService.new(@user)
  end

  def test_should_return_requested_number_of_gcal_events
    max_results = GoogleCalendarApi::EventFetchService::MAX_RESULTS_TO_BE_FETCHED
    required_event_items_count = 2
    mock_gcal_events_listing(@user, item_count: required_event_items_count)
    events = @event_fetch_service.process
    count = events.count
    assert required_event_items_count <= count && count <= max_results
  end
end

In the EventFetchService we are making actual API calls to the Google APIs. But given that in the above test case we have mocked that Google endpoint, whenever an API call is made to that endpoint, the mocked response will be returned.

The beauty of keeping this DRY is that, we can handle other business logic related to Google or other 3rd party APIs by reusing these support methods.

Example, let's say we want to verify the status after creating a new Google Calendar event. For that we can do something like this:

def test_should_return_confirmed_status_when_event_is_successfully_inserted_to_gcal
  body = successful_event_creation_response(@user, @booking, @internal_booking, @meeting_type)
  stub_gcal_api_request(body)
  event = @event_creation_service.process
  assert_equal "confirmed", event.status
end

The successful_event_creation_response will return a JSON output. The JSON value will be hardcoded in our google_calendar_api_support file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment