🧹 Remove axios, use generics for send-request in insomnia-testing (#3949)

This commit is contained in:
Opender Singh authored and GitHub committed 2021-08-27 04:02:59 +00:00
1 parent 422d5a809c
commit 39eb725a55
11 files changed
+95 -269

No files matched your search

@@ -53,7 +53,7 @@ async function sendAndTransform(requestId: string, environmentId?: string) {
try {
plugins.ignorePlugin('insomnia-plugin-kong-bundle');
const res = await send(requestId, environmentId);
const headersObj = {};
const headersObj: Record<string, string> = {};
for (const h of res.headers || []) {
const name = h.name || '';
@@ -255,7 +255,7 @@ class WrapperUnitTest extends PureComponent<Props, State> {
}
async _runTests(unitTests: UnitTest[]) {
const { requests, activeWorkspace, activeEnvironment } = this.props.wrapperProps;
const { activeWorkspace, activeEnvironment } = this.props.wrapperProps;
if (!activeWorkspace) {
return;
@@ -283,14 +283,14 @@ class WrapperUnitTest extends PureComponent<Props, State> {
},
]);
const sendRequest = getSendRequestCallback(activeEnvironment?._id);
let results;
try {
results = await runTests(src, {
requests,
// @ts-expect-error -- TSCONVERSION
sendRequest,
const results = await runTests(src, { sendRequest });
await models.unitTestResult.create({
results,
parentId: activeWorkspace._id,
});
this.setState({ testsRunning: null });
} catch (err) {
// Set the state after a timeout so the user still sees the loading state
setTimeout(() => {
@@ -301,14 +301,6 @@ class WrapperUnitTest extends PureComponent<Props, State> {
}, 400);
return;
}
await models.unitTestResult.create({
results,
parentId: activeWorkspace._id,
});
this.setState({
testsRunning: null,
});
}
buildSelectableRequests(): {
-13
View File
@@ -451,14 +451,6 @@
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
"dev": true
},
"axios": {
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
"integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
"requires": {
"follow-redirects": "^1.10.0"
}
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
@@ -1789,11 +1781,6 @@
"readable-stream": "^2.3.6"
}
},
"follow-redirects": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.2.tgz",
"integrity": "sha512-6mPTgLxYm3r6Bkkg0vNM0HTjfGrOEtsfbhagQvbxDEsEkpNhw582upBaoRZylzen6krEmxXJgt9Ju6HiI4O7BA=="
},
"for-in": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
-1
View File
@@ -34,7 +34,6 @@
"webpack-cli": "^3.3.11"
},
"dependencies": {
"axios": "^0.21.1",
"chai": "^4.3.4",
"mkdirp": "^1.0.4",
"mocha": "^8.3.2"
@@ -1,47 +0,0 @@
import type { AxiosRequestConfig, AxiosResponse, AxiosStatic, Method } from 'axios';
const mockResponses: { [key: string]: AxiosResponse } = {};
const key = (method?: Method, url?: string) => (
`${method?.toLowerCase()}:${url?.toLowerCase()}`
);
type SetResponse = <T>(method?: Method, url?: string, response?: AxiosResponse<T>) => void;
const axios = jest.genMockFromModule<AxiosStatic & { __setResponse: SetResponse }>('axios');
const __setResponse: SetResponse = (method, url, response) => {
if (!method) {
return;
}
if (!url) {
return;
}
if (!response) {
return;
}
mockResponses[key(method, url)] = response;
};
axios.__setResponse = __setResponse;
const request = async <T, U = AxiosResponse<T>>({ method, url }: AxiosRequestConfig) => {
const k = key(method, url);
const resp = mockResponses[k] as unknown as U;
if (!resp) {
throw new Error(
`Could not find mock axios response for ${k}. Options are [${Object.keys(mockResponses).join(
', ',
)}]`,
);
}
return Promise.resolve<U>(resp);
};
axios.request = request;
export type AxiosMock = typeof axios;
export default axios;
@@ -1,12 +1,7 @@
import _axios from 'axios';
import { AxiosMock } from '../__mocks__/axios';
import { generate } from '../generate';
import { runTests } from '../run';
const axios = _axios as AxiosMock;
jest.mock('axios');
import { mockedSendRequest, mockedSendRequestMultiple } from '../test-helpers/send-request-mock';
describe('integration', () => {
it('generates and runs basic tests', async () => {
@@ -29,7 +24,10 @@ describe('integration', () => {
],
},
]);
const { stats } = await runTests(testSrc);
const sendRequest = mockedSendRequest();
const { stats, failures } = await runTests(testSrc, { sendRequest });
expect(failures).toEqual([]);
expect(stats.tests).toBe(2);
expect(stats.failures).toBe(0);
expect(stats.passes).toBe(2);
@@ -55,44 +53,16 @@ describe('integration', () => {
],
},
]);
const { stats, failures } = await runTests(testSrc);
expect(failures).toEqual([]);
expect(stats.tests).toBe(2);
expect(stats.failures).toBe(0);
expect(stats.passes).toBe(2);
const { stats: stats2 } = await runTests(testSrc);
expect(failures).toEqual([]);
expect(stats2.tests).toBe(2);
expect(stats2.failures).toBe(0);
expect(stats2.passes).toBe(2);
});
it('generates and runs more than once', async () => {
const testSrc = generate([
{
name: 'Example TestSuite',
suites: [],
tests: [
{
name: 'should return -1 when the value is not present',
code:
'expect([1, 2, 3].indexOf(4)).to.equal(-1);\nexpect(true).to.be.true;',
defaultRequestId: null,
},
{
name: 'is an empty test',
code: '',
defaultRequestId: null,
},
],
},
]);
const { stats, failures } = await runTests(testSrc);
const sendRequest = mockedSendRequest();
const { stats, failures } = await runTests(testSrc, { sendRequest });
expect(failures).toEqual([]);
expect(stats.tests).toBe(2);
expect(stats.failures).toBe(0);
expect(stats.passes).toBe(2);
const { stats: stats2, failures: failures2 } = await runTests(testSrc);
const { stats: stats2, failures: failures2 } = await runTests(testSrc, { sendRequest });
expect(failures2).toEqual([]);
expect(stats2.tests).toBe(2);
expect(stats2.failures).toBe(0);
@@ -100,27 +70,17 @@ describe('integration', () => {
});
it('sends an HTTP request', async () => {
axios.__setResponse('GET', '200.insomnia.rest', {
const response1 = {
status: 200,
headers: {
'content-type': 'application/json',
},
data: {
foo: 'bar',
},
statusText: '',
config: {},
});
statusMessage: 'abc',
};
axios.__setResponse('GET', '301.insomnia.rest', {
const response2 = {
status: 301,
headers: {
location: '/blog',
},
data: undefined,
statusText: '',
config: {},
});
statusMessage: 'def',
};
const sendRequest = mockedSendRequestMultiple(response1, response2);
const testSrc = generate([
{
@@ -131,36 +91,33 @@ describe('integration', () => {
name: 'Tests referencing request by ID',
defaultRequestId: null,
code: [
'const resp = await insomnia.send(\'req_123\');',
'expect(resp.status).to.equal(301);',
'const resp = await insomnia.send(\'foo\');',
'expect(resp.status).to.equal(200);',
'expect(resp.statusMessage).to.equal(\'abc\');',
].join('\n'),
},
{
name: 'Tests referencing default request',
defaultRequestId: 'req_123',
defaultRequestId: 'bar',
code: [
'const resp = await insomnia.send();',
'expect(resp.status).to.equal(301);',
'expect(resp.statusMessage).to.equal(\'def\');',
].join('\n'),
},
],
},
]);
const { stats, failures, passes } = await runTests(testSrc, {
requests: [
{
_id: 'req_123',
url: '301.insomnia.rest',
method: 'get',
},
],
});
const { stats, failures, passes } = await runTests(testSrc, { sendRequest });
expect(failures).toEqual([]);
expect(passes.length).toBe(2);
expect(stats.tests).toBe(2);
expect(stats.failures).toBe(0);
expect(stats.passes).toBe(2);
expect(sendRequest).toHaveBeenNthCalledWith(1, 'foo');
expect(sendRequest).toHaveBeenNthCalledWith(2, 'bar');
});
});
+15 -89
View File
@@ -1,38 +1,7 @@
import axios, { Method } from 'axios';
// @ts-expect-error we're pulling straight out of the lib here in a way that is not intended, but axios does not directly expose this adapter
import httpAdapter from 'axios/lib/adapters/http';
export type SendRequestCallback<TResponse> = (requestId: string) => Promise<TResponse>;
export interface Request {
_id: string;
url?: string;
method?: string;
body?: {
text?: string;
};
headers?: {
name: string;
value: string;
description?: string;
disabled?: boolean;
}[];
}
export interface Response {
status: number;
statusText: string;
data: {
[key: string]: unknown;
};
headers: {
[key: string]: string;
};
}
export type SendRequestCallback = (requestId: string) => Promise<Response>;
export interface InsomniaOptions {
requests?: Request[];
sendRequest?: SendRequestCallback;
export interface InsomniaOptions<TResponse> {
sendRequest: SendRequestCallback<TResponse>;
bail?: boolean;
keepFile?: boolean;
testFilter?: string;
@@ -43,81 +12,38 @@ export interface InsomniaOptions {
* tests, and will provide a bunch of utility functions for sending
* requests, etc.
*/
export default class Insomnia {
requests: Request[];
export class Insomnia<TResponse = {}> {
activeRequestId: string | null;
activeEnvironmentId: string | null = null;
sendRequest: SendRequestCallback | null;
sendRequest: SendRequestCallback<TResponse>;
constructor(options: InsomniaOptions = {}) {
this.requests = options.requests || [];
this.sendRequest = options.sendRequest || null; // Things that are set per test
constructor(options: InsomniaOptions<TResponse>) {
this.sendRequest = options.sendRequest; // Things that are set per test
this.activeRequestId = null;
}
setActiveRequestId(id: string): void {
setActiveRequestId(id: string) {
this.activeRequestId = id;
}
clearActiveRequest(): void {
clearActiveRequest() {
this.activeRequestId = null;
}
/**
*
* @param reqId - request ID to send. Specifying nothing will send the active request
* @returns {Promise<{headers: *, data: *, statusText: (string|string), status: *}>}
*/
async send(reqId: string | null = null): Promise<Response> {
async send(reqId: string | null = null) {
// Default to active request if nothing is specified
reqId = reqId || this.activeRequestId;
const { sendRequest } = this;
const requestId = reqId || this.activeRequestId;
if (typeof sendRequest === 'function' && typeof reqId === 'string') {
return sendRequest(reqId);
if (!requestId) {
throw new Error('No selected request');
}
const request = this.requests.find(request => request._id === reqId);
if (!request) {
throw new Error('Request not provided to test');
}
const { headers, url = '', method, body } = request;
const axiosHeaders = headers?.reduce((accumulator, { disabled, name, value }) => {
return {
...accumulator,
...(disabled ? {} : { [name]: value }),
};
});
const response = await axios.request({
url,
method: (method as Method) || 'GET',
data: body?.text,
headers: axiosHeaders,
// Don't follow redirects,
maxRedirects: 0,
// Don't throw errors on status != 200
validateStatus: () => true,
// Force NodeJS adapter or Electron will default to XMLHttpRequest
adapter: httpAdapter,
});
console.log('[tests] Received response', {
response: response,
});
return {
status: response.status,
statusText: response.statusText,
data: response.data,
headers: response.headers,
};
const result = await this.sendRequest(requestId);
return result;
}
}
+13 -19
View File
@@ -1,4 +1,5 @@
import { TestResults } from './entities';
import { mocked } from 'ts-jest/utils';
import { SendRequestCallback } from './insomnia';
import { runTests } from './run';
@@ -27,44 +28,37 @@ describe('Example', () => {
`;
describe('run', () => {
const getMockedSendRequest = () => mocked<SendRequestCallback<{status: number}>>(jest.fn().mockResolvedValue({ status: 200 }));
it('runs a mocha suite', async () => {
const { stats } = await runTests(exampleTest);
const { stats } = await runTests(exampleTest, { sendRequest: getMockedSendRequest() });
expect(stats.passes).toBe(1);
expect(stats.tests).toBe(2);
expect(stats.failures).toBe(1);
});
it('runs empty mocha suite', async () => {
const { stats } = await runTests(exampleEmptySuite);
const { stats } = await runTests(exampleEmptySuite, { sendRequest: getMockedSendRequest() });
expect(stats.passes).toBe(0);
expect(stats.tests).toBe(0);
expect(stats.failures).toBe(0);
});
it('works on multiple files', async () => {
const { stats } = await runTests([exampleTest, exampleTest]);
const { stats } = await runTests([exampleTest, exampleTest], { sendRequest: getMockedSendRequest() });
expect(stats.passes).toBe(2);
expect(stats.tests).toBe(4);
expect(stats.failures).toBe(2);
});
it('calls sendRequest() callback', async () => {
const sendRequest = jest.fn(() =>
Promise.resolve({
status: 200,
}),
) as unknown as SendRequestCallback;
const { stats }: TestResults = await runTests(
const sendRequest = getMockedSendRequest();
const { stats } = await runTests(
exampleTestWithRequest,
{
requests: [
{
_id: 'req_123',
},
],
sendRequest,
},
{ sendRequest },
);
expect(sendRequest).toHaveBeenCalledWith('req_123');
expect(stats.passes).toBe(1);
});
@@ -73,7 +67,7 @@ describe('run', () => {
let err;
try {
await runTests('this is invalid');
await runTests('this is invalid', { sendRequest: getMockedSendRequest() });
} catch (e) {
err = e;
}
+15 -14
View File
@@ -5,7 +5,8 @@ import Mocha, { Reporter, ReporterConstructor } from 'mocha';
import { tmpdir } from 'os';
import { join } from 'path';
import Insomnia, { InsomniaOptions } from './insomnia';
import { TestResults } from './entities';
import { Insomnia, InsomniaOptions } from './insomnia';
import { JavaScriptReporter } from './javascript-reporter';
declare global {
@@ -17,13 +18,12 @@ declare global {
}
}
const runInternal = async <T>(
const runInternal = async <TReturn, TNetworkResponse>(
testSrc: string | string[],
options: InsomniaOptions,
options: InsomniaOptions<TNetworkResponse>,
reporter: Reporter | ReporterConstructor = 'spec',
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- type not available, and postponing anyway until the impending move to all jest (and no mocha)
extractResult: (runner: { [key: string]: any }) => T,
): Promise<T> => new Promise((resolve, reject) => {
extractResult: (runner: Mocha.Runner) => TReturn,
) => new Promise<TReturn>((resolve, reject) => {
const { bail, keepFile, testFilter } = options;
// Add global `insomnia` helper.
@@ -83,32 +83,33 @@ const writeTempFile = (sourceCode: string) => {
return path;
};
type CliOptions = InsomniaOptions & {
type CliOptions<TNetworkResponse> = InsomniaOptions<TNetworkResponse> & {
reporter?: Reporter
}
/**
* Run a test file using Mocha
*/
export const runTestsCli = async (
export const runTestsCli = async <TNetworkResponse>(
testSrc: string | string[],
{ reporter, ...options }: CliOptions = {},
{ reporter, ...options }: CliOptions<TNetworkResponse>,
) => runInternal(
testSrc,
options,
reporter,
runner => !runner.stats.failures,
runner => !Boolean(runner.stats?.failures),
);
/**
* Run a test file using Mocha and returns JS results
*/
export const runTests = async <T>(
export const runTests = async <TNetworkResponse>(
testSrc: string | string[],
options: InsomniaOptions = {},
) => runInternal<T>(
options: InsomniaOptions<TNetworkResponse>,
) => runInternal(
testSrc,
options,
JavaScriptReporter,
runner => runner.testResults,
// @ts-expect-error the `testResults` property is added onto the runner by the JavascriptReporter
runner => runner.testResults as TestResults,
);
@@ -0,0 +1,17 @@
import { mocked } from 'ts-jest/utils';
import { SendRequestCallback } from '../run/insomnia';
export const mockedSendRequest = <TResp extends {status: number}>(response?: TResp) => {
return mocked<SendRequestCallback<TResp>>(jest.fn()).mockResolvedValue(response || { status: 200 } as TResp);
};
export const mockedSendRequestMultiple = <TResp>(...responses: TResp[]) => {
let mock = mocked<SendRequestCallback<TResp>>(jest.fn());
responses.forEach(response => {
mock = mock.mockResolvedValueOnce(response);
});
return mock;
};
@@ -10,7 +10,7 @@
],
"exclude": [
"**/fixtures",
"**/__mocks__",
"**/test-helpers",
"**/*.test.ts"
]
}