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.