Last active
February 24, 2021 02:45
-
-
Save bennettscience/e405c0f70e633c71e9063fdc0d98bf38 to your computer and use it in GitHub Desktop.
A simple Mocking framework in Google Apps Script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Fixtures provide a conventient way to define expected data strucures from API calls. This | |
* works based on JSON keys and gives a flexible, namespaced access for quickly making Mocks. | |
*/ | |
const fixtures = (function() { | |
const init = function() { | |
return this; | |
} | |
// Can be generic method requests, status codes, or mirror your target endpoints for library creation. | |
const requests = { | |
"get": { | |
"method": "ANY", | |
"endpoint": "400", | |
"data": "Hello world!", | |
"status_code": 200 | |
}, | |
"401_invalid_access_token": { | |
"method": "ANY", | |
"endpoint": "401_invalid_access_token", | |
"data": {}, | |
"headers": { | |
"WWW-Authenticate": "Bearer realm=\"canvas-lms\"" | |
}, | |
"status_code": 401 | |
}, | |
"401_unauthorized": { | |
"method": "ANY", | |
"endpoint": "401_unauthorized", | |
"data": {}, | |
"status_code": 401 | |
}, | |
// ...add more | |
return { | |
requests: requests, | |
} | |
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* A Mock class accepts a fixture and assumes the expected data structure in the test. The `fetch` | |
* method allows for mocked access to the Apps Script UrlFetchApp class so tests can be written | |
* as if they were in production. | |
*/ | |
class Mock { | |
constructor(params) { | |
for(var name in params) { | |
Object.defineProperty(this, name, { | |
value: params[name], | |
enumerable: true, | |
configurable: true | |
}) | |
} | |
} | |
fetch() { | |
return { | |
getContentText: () => this.data, | |
getResponseCode: () => Number(this.status_code) | |
} | |
} | |
} | |
/** | |
* Factory to create Mock objects. | |
* @param {string} requirement Fixture namespace to access | |
* @param {string} endpoint Specific key within the fixture | |
*/ | |
function registerMock(requirement, endpoint) { | |
const f = fixtures.init(); | |
let mock = new Mock(f[requirement][endpoint]) | |
console.log(mock instanceof Mock) | |
return mock | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* This class is specific to working with the CanvasLMS API, but it could be generalized for | |
* any kind of API work. When Canvas-specific objects are created, a Requester instance is attached | |
* as an attribute and it handles calls from the Canvas object directly. The user won't really | |
* interact with Requester unless they're building a custom call. | |
* | |
* Using dependency injection, requests can be mocked in unit tests (example below). UrlFetchApp, | |
* if defined at instantiation, can be a Mock object which will return expected results without | |
* requiring major modifications to Requester. | |
* | |
* @param {string} baseUrl The TLD to request | |
* @param {string} accessToken Valid API key | |
* @param {object} UrlFetchApp_ Inject a Mock class for unit testing | |
*/ | |
class Requester { | |
constructor(baseUrl, accessToken, {UrlFetchApp_=UrlFetchApp}={}) { | |
this._baseUrl = baseUrl; | |
this._accessToken = accessToken; | |
this.UrlFetchApp = UrlFetchApp_; | |
} | |
get_request(url, headers, params=null) { | |
const opts = { | |
"method": "GET", | |
"contentType": "application/json", | |
"headers": headers, | |
"muteHttpExceptions": true, | |
} | |
return this.UrlFetchApp.fetch(url, opts); | |
} | |
/** | |
* @param {string} method HTTP protocol | |
* @param {string} endpoint API resource | |
* @param {object} headers Optional headers to include | |
* @param {object} payload Optional data to send to the API | |
* | |
* @returns {object} response | |
*/ | |
request(method, endpoint, headers=null, payload={}) { | |
let req_method, response; | |
method = method.toUpperCase(); | |
const full_url = `${this._baseUrl}/api/v1/${endpoint}`; | |
if (!headers) { | |
headers = { | |
"Authorization": "Bearer " + this._accessToken | |
} | |
} | |
if(method === "GET") { | |
req_method = this.get_request.bind(this); | |
} else { | |
throw new Error(`We're not ready to do more yet.`); | |
} | |
response = req_method(full_url, headers, payload) | |
return response.getContentText(); | |
} | |
} | |
/** | |
* Requester.request() can be tested using a mock by injecting the Mock object. | |
*/ | |
function test() { | |
let M = register_uris("requests", "get"); | |
let requester = new Requester("https://www.example.com", "abc123", {UrlFetchApp_: Mock}); | |
let resp = Requester.request("GET", "foo/bar"); | |
console.log(resp) // => "Hello World" | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment