Last active
October 4, 2016 21:12
-
-
Save bryan-c-oconnell/6f5ec5d2c4b17f1ceb9e to your computer and use it in GitHub Desktop.
bryanoconnell.blogspot.com - Exploring TypeScript (an API testing module)
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
module BOC.ApiTester { | |
export class ApiTests { | |
private results: ResultsLog; | |
private timeTracker: Timer; | |
private outputElement: HTMLElement; | |
static baseUrl: string = "http://jsonplaceholder.typicode.com"; | |
constructor(elementIdToDisplayResults: string) { | |
this.results = new ResultsLog(); | |
this.timeTracker = new Timer(); | |
this.outputElement = document.getElementById(elementIdToDisplayResults); | |
this.outputElement.innerHTML = ""; // Reset to remove any previous results. | |
} | |
public run(): void { | |
this.timeTracker.start(); | |
try { | |
// GET examples - Get all Users, and pick a random User for further tests: | |
var allUserRecords: JSON = this.apiCall("GET",(ApiTests.baseUrl + "/users")); | |
var randomUser: number = this.getRandomNumberInRange(0,(Object.keys(allUserRecords).length - 1)); | |
var singleUser = allUserRecords[randomUser]; | |
// POST example - assign a new Todo to the User: | |
var newToDo: string = JSON.stringify({ | |
userId: singleUser.id, | |
title: "Please delete the last post you made; it is no longer needed.", | |
completed: false | |
}); | |
var createNewToDo: JSON = this.apiCall("POST",(ApiTests.baseUrl + "/todos"), newToDo); | |
// DELETE example - complete the ToDo that was assigned to the user by deleting their last Post: | |
var allPostsForUser: JSON = this.apiCall("GET",(ApiTests.baseUrl + "/posts?userId=" + singleUser.id)); | |
var lastPost = allPostsForUser[(Object.keys(allPostsForUser).length - 1)]; | |
var deleteLastPost: JSON = this.apiCall("DELETE",(ApiTests.baseUrl + "/posts/" + lastPost.id)); | |
// PUT example - User has accomplished the requested task, so let's update the 'completed' status of one of their open ToDos. | |
// NOTE: This API does not actually save changes, so we can't update the record we "created" in the POST example. | |
var incompleteToDosForUser: JSON = this.apiCall("GET",(ApiTests.baseUrl + "/todos?completed=false&userId=" + singleUser.id)); | |
var randomToDo: number = this.getRandomNumberInRange(0,(Object.keys(incompleteToDosForUser).length - 1)); | |
var updatedToDo = incompleteToDosForUser[randomToDo]; | |
updatedToDo.completed = true; | |
var completedToDo: JSON = this.apiCall("PUT",(ApiTests.baseUrl + "/todos/" + updatedToDo.id), JSON.stringify(updatedToDo)); | |
} | |
catch (error) { | |
this.results.addEntry("Error occurred: " + error + "; aborting test execution process."); | |
} | |
finally { | |
this.timeTracker.stop(); | |
} | |
} | |
public displayResults(): void { | |
this.outputElement.innerHTML += this.results.getResultsAsHtml(); | |
this.outputElement.innerHTML += this.timeTracker.getRuntimeAsHtml(); | |
} | |
private apiCall(action: string, endpointUrl: string, contents: string = ""): JSON { | |
var requestObject: ApiRequest = new ApiRequest(action, endpointUrl); | |
var response: string = requestObject.makeCall(contents); | |
if (requestObject.requestSucceeded) { | |
this.results.addEntry(action + " - " + endpointUrl + " - PASS"); | |
this.results.addEntry(response); | |
return JSON.parse(response); | |
} | |
else { | |
throw (action + " - " + endpointUrl + " failed to return the expected response."); | |
} | |
} | |
// Returns a random number between min (inclusive) and max (exclusive) | |
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random | |
private getRandomNumberInRange(min: number, max: number): number { | |
return Math.floor(Math.random() * (max - min)) + min; | |
} | |
} | |
export class ResultsLog { | |
private ListOfResults: Array<string>; | |
constructor() { | |
this.reset(); | |
} | |
public addEntry(entry: string): void { | |
this.ListOfResults.push(entry); | |
} | |
public getResultsAsHtml() { | |
var i: number = 0; | |
var entry: string = ""; | |
var resultsHtml: string = ""; | |
while (entry = this.ListOfResults[i++]) { | |
resultsHtml += "<p>" + entry + "</p>" | |
} | |
return resultsHtml; | |
} | |
public reset(): void { | |
this.ListOfResults = []; | |
} | |
} | |
export class Timer { | |
private timerIsRunning: boolean; | |
private startTime: string; | |
private endTime: string; | |
private totalRuntime: number; | |
constructor() { | |
this.timerIsRunning = false; | |
this.resetTimeData(); | |
} | |
private resetTimeData(): void { | |
this.totalRuntime = 0; | |
var currentTime: string = new Date().toLocaleString(); | |
this.startTime = currentTime; | |
this.endTime = currentTime; | |
} | |
public start(): void { | |
if (!this.timerIsRunning) { | |
this.timerIsRunning = true; | |
this.resetTimeData(); | |
} | |
} | |
public stop(): void { | |
if (this.timerIsRunning) { | |
this.timerIsRunning = false; | |
this.endTime = new Date().toLocaleString(); | |
this.calculateTotalRuntime(); | |
} | |
} | |
private calculateTotalRuntime(): void { | |
var begin: number = new Date(this.startTime).getTime(); | |
var end: number = new Date(this.endTime).getTime(); | |
this.totalRuntime = end - begin; | |
} | |
public getRuntimeAsHtml(): string { | |
var runtimeInMilliseconds: number = this.totalRuntime; | |
// Calcuates the number of minutes the Timer ran for, and removes that from total runtime. | |
var minutes: number = Math.floor(runtimeInMilliseconds / 1000 / 60); | |
runtimeInMilliseconds -= (minutes * 1000 * 60); | |
// Calculates the number of remaining seconds the Timer ran for AFTER removing the number of minutes from total runtime. | |
var seconds: number = Math.floor(runtimeInMilliseconds / 1000); | |
var runtimeMessage: string = "<p>Total runtime: " + minutes.toString() + " minutes, " + seconds.toString() + " seconds.</p>"; | |
return runtimeMessage; | |
} | |
} | |
export class ApiRequest { | |
private actionType: string; | |
private urlToCall: string; | |
private responseData: string; | |
private _requestSucceeded: boolean; | |
constructor(method: string, targetUrl: string) { | |
this.actionType = method; | |
this.urlToCall = targetUrl; | |
this._requestSucceeded = false; | |
} | |
private createRequestObject(): XMLHttpRequest { | |
var xhr: XMLHttpRequest = new XMLHttpRequest(); | |
// The tests need to run in the specified order, because some steps create or collect data that later steps depend on. | |
// Setting the last parameter here to 'true' will cause asynchronous calls to occur, which will interrupt that order. | |
xhr.open(this.actionType, this.urlToCall, false); | |
// Tells the server that the call was made for AJAX purposes. | |
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); | |
// Circumvents the browser's desire to cache duplicate GET calls. | |
xhr.setRequestHeader("If-Modified-Since", "Sun, 6 Jun 1980 00:00:00 GMT"); | |
// Asks to get data back as JSON. | |
xhr.setRequestHeader("Content-Type", "application/json"); | |
return xhr; | |
} | |
public makeCall(bodyContents: string = ""): string { | |
var xhr: XMLHttpRequest = this.createRequestObject(); | |
xhr.onerror = (err) => { | |
this.responseData = "Error occurred calling " + this.urlToCall + ". Message: " + err.message; | |
this._requestSucceeded = false; | |
} | |
// http://stackoverflow.com/questions/12756423/is-there-an-alias-for-this-in-typescript - #3 | |
xhr.onload = () => { | |
if (this.requestWasSuccessful(xhr.status)) { | |
this.responseData = xhr.responseText; | |
this._requestSucceeded = true; | |
} | |
else { | |
this.responseData = "API responded with an error status: " + xhr.status.toString() + " - " + xhr.statusText; | |
this._requestSucceeded = false; | |
} | |
}; | |
xhr.send(bodyContents); | |
return this.responseData; | |
} | |
// http://www.restapitutorial.com/httpstatuscodes.html | |
private requestWasSuccessful(statusCode: number): boolean { | |
return (((statusCode > 99) && (statusCode < 299)) ? true : false); | |
} | |
get requestSucceeded(): boolean { | |
return this._requestSucceeded; | |
} | |
} | |
} |
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
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8" /> | |
<title>TypeScript API Tester</title> | |
<link rel="stylesheet" href="app.css" type="text/css" /> | |
<script src="api-tester.js"></script> | |
</head> | |
<body> | |
<h1>TypeScript API Tester</h1> | |
<p> | |
<input type="button" value="Run Tests" onclick="runTests()" /> | |
</p> | |
<div id="displayResults"></div> | |
</body> | |
</html> | |
<script type="text/javascript"> | |
function runTests() { | |
var myTests = new BOC.ApiTester.ApiTests("displayResults"); | |
alert("Running tests ...") | |
myTests.run(); | |
myTests.displayResults(); | |
alert("Test process complete."); | |
} | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment