Skip to content

Commit

Permalink
test: adding test helper funcitons
Browse files Browse the repository at this point in the history
  • Loading branch information
manchuck committed Jun 17, 2024
1 parent a14b999 commit 4ea9add
Show file tree
Hide file tree
Showing 6 changed files with 227 additions and 0 deletions.
59 changes: 59 additions & 0 deletions testHelpers/getResults.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// TS gets a but overloaded and will not address the parameters correctly
// so we need to define these types here to trick it
type ClientMethod = (...args: unknown[]) => Promise<unknown>;
type GeneratorClientMethod = (...args: unknown[]) => AsyncGenerator<unknown>;

/**
* A helper function to get the results from a client method
*
* Since some functions are generators and some are not, we need to handle them differently.
* This function will call the client method and return the results.
*
* @template T
* @param {boolean} generator - Whether the client method is a generator
* @param {T} client - The client to call the method on
* @param {string} clientMethod - The method to call on the clientMethod
* @param {Array<unknown> }args - The arguments to pass to the client method
*
* @return {Promise<unknown | Array<unknown>>} The results of the client method
*/
export const getResults = async <T>(
generator: boolean,
client: T,
clientMethod: keyof T,
args: [unknown?, unknown?, unknown?] = [],
): Promise<unknown | Array<unknown>> => {
if (!client) {
throw new Error('Client is not defined');
}

if (client[clientMethod] === undefined) {
throw new Error(
`Method ${client.constructor.name}::${String(
clientMethod,
)} is not defined`,
);
}

if (typeof client[clientMethod] !== 'function') {
throw new Error(
`Method ${client.constructor.name}::${String(
clientMethod,
)} is not a function`,
);
}

if (!generator) {
return await (client[clientMethod] as ClientMethod)(...args);
}

const results: Array<unknown> = [];

// eslint-disable-next-line max-len
for await (const result of (client[clientMethod] as GeneratorClientMethod)(
...args,
)) {
results.push(result);
}
return results;
};
4 changes: 4 additions & 0 deletions testHelpers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './types';
export * from './getResults';
export * from './key';
export * from './vonageTest';
95 changes: 95 additions & 0 deletions testHelpers/types/SDKTestCase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import nock from 'nock';

/**
* A Tuple that contains the status code of the response and the nock interceptor function
*/
type TestResponse = [
/**
* The status code of the response
*/
number,

/**
* The nock interceptor body response
*
* This can either be the body of the response or a nock interceptor function
*/
nock.InterceptFunction?,
];

/**
* A Tuple that contains the method, path, and body of a request nock will intercept.
*/
type TestRequest = [
/**
* The method of the request
*/
'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH',

/**
* The path of the request
*/
string,

/**
* The body of the request
*/
nock.RequestBodyMatcher?,
];

export type SDKTestCase<T> = {
/**
* The label for the test case
*/
label: string;

/**
* The base URL for the API
*/
baseUrl: string;

/**
* The headers that are expected to be sent with the request
*/
reqHeaders: Record<string, nock.RequestHeaderMatcher>;

/**
* List of Nock Responses that will be used to mock the API
*/
responses: TestResponse[];

/**
* List of Nock Requests that will be used to mock the API
*/
requests: TestRequest[];

/**
* The client that will be used to make the request
*/
client: T;

/**
* The method that will be called on the client
*/
clientMethod: keyof T;

/**
* The parameters that will be passed to the client method
*/
parameters?: [unknown, unknown, unknown];

/**
* Tell the test that the client method is a generator
*/
generator: boolean;

/**
* Tell the test that the response is going to be an error
*/
error: boolean;

/**
* The expected response from the call to the client method
*/
expected: unknown;
};
16 changes: 16 additions & 0 deletions testHelpers/types/TestTuple.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { SDKTestCase } from './SDKTestCase';

/**
* TestTuple is a tuple that contains the name of the test suite and an array of SDKTestCase objects.
*/
export type TestTuple = {
/**
* The name of the test suite. (Used for the describe block in the test file)
*/
name: string;

/**
* An array of SDKTestCase objects.
*/
tests: Array<SDKTestCase<unknown>>;
};
2 changes: 2 additions & 0 deletions testHelpers/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './SDKTestCase';
export * from './TestTuple';
51 changes: 51 additions & 0 deletions testHelpers/vonageTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/* eslint-disable jest/no-export */
import nock from 'nock';
import { SDKTestCase, TestTuple } from './types';
import { getResults } from './getResults';

/**
* A Wrapper function to build out tests for Vonage SDKs
*
* @param {TestTuple} testDataSets - An array of test data sets
*/
export const VonageTest = <T>(testDataSets: TestTuple[]) => {
describe.each<TestTuple>(testDataSets)('$label', ({ tests }) => {
test.each<SDKTestCase<T>>(tests)(
'Can $label',
async ({
baseUrl,
reqHeaders,
generator,
requests,
responses,
client,
clientMethod,
parameters,
expected,
}) => {
const scope = nock(baseUrl, {
reqheaders: reqHeaders,
});

requests.forEach((request, index) => {
(scope as nock.Scope)
.intercept(...request)
.reply(...responses[index]);
});

const results = await getResults<T>(
generator,
client,
clientMethod,
parameters,
);

expect(results).toEqual(expected);
expect(nock.isDone()).toBeTruthy();

expect(results).toEqual(expected);
expect(nock.isDone()).toBeTruthy();
},
);
});
};

0 comments on commit 4ea9add

Please sign in to comment.