diff --git a/packages/insomnia-app/app/common/send-request.ts b/packages/insomnia-app/app/common/send-request.ts index c1c5d38b65..a906e48b2b 100644 --- a/packages/insomnia-app/app/common/send-request.ts +++ b/packages/insomnia-app/app/common/send-request.ts @@ -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 = {}; for (const h of res.headers || []) { const name = h.name || ''; diff --git a/packages/insomnia-app/app/ui/components/wrapper-unit-test.tsx b/packages/insomnia-app/app/ui/components/wrapper-unit-test.tsx index 18823f2d0f..da44f08acf 100644 --- a/packages/insomnia-app/app/ui/components/wrapper-unit-test.tsx +++ b/packages/insomnia-app/app/ui/components/wrapper-unit-test.tsx @@ -255,7 +255,7 @@ class WrapperUnitTest extends PureComponent { } 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 { }, ]); 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 { }, 400); return; } - - await models.unitTestResult.create({ - results, - parentId: activeWorkspace._id, - }); - this.setState({ - testsRunning: null, - }); } buildSelectableRequests(): { diff --git a/packages/insomnia-testing/package-lock.json b/packages/insomnia-testing/package-lock.json index 58c7cc4cd7..5749ef0468 100644 --- a/packages/insomnia-testing/package-lock.json +++ b/packages/insomnia-testing/package-lock.json @@ -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", diff --git a/packages/insomnia-testing/package.json b/packages/insomnia-testing/package.json index fd9dd15f3b..59a21c1a1d 100644 --- a/packages/insomnia-testing/package.json +++ b/packages/insomnia-testing/package.json @@ -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" diff --git a/packages/insomnia-testing/src/__mocks__/axios.ts b/packages/insomnia-testing/src/__mocks__/axios.ts deleted file mode 100644 index 9a4799706c..0000000000 --- a/packages/insomnia-testing/src/__mocks__/axios.ts +++ /dev/null @@ -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 = (method?: Method, url?: string, response?: AxiosResponse) => void; - -const axios = jest.genMockFromModule('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 >({ 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(resp); -}; - -axios.request = request; - -export type AxiosMock = typeof axios; - -export default axios; diff --git a/packages/insomnia-testing/src/integration/integration.test.ts b/packages/insomnia-testing/src/integration/integration.test.ts index 1c11b2e07a..12c5e71bfc 100644 --- a/packages/insomnia-testing/src/integration/integration.test.ts +++ b/packages/insomnia-testing/src/integration/integration.test.ts @@ -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'); }); }); diff --git a/packages/insomnia-testing/src/run/insomnia.ts b/packages/insomnia-testing/src/run/insomnia.ts index 19ae7d5f4f..8f6d672450 100644 --- a/packages/insomnia-testing/src/run/insomnia.ts +++ b/packages/insomnia-testing/src/run/insomnia.ts @@ -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 = (requestId: string) => Promise; -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; - -export interface InsomniaOptions { - requests?: Request[]; - sendRequest?: SendRequestCallback; +export interface InsomniaOptions { + sendRequest: SendRequestCallback; 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 { activeRequestId: string | null; activeEnvironmentId: string | null = null; - sendRequest: SendRequestCallback | null; + sendRequest: SendRequestCallback; - constructor(options: InsomniaOptions = {}) { - this.requests = options.requests || []; - this.sendRequest = options.sendRequest || null; // Things that are set per test + constructor(options: InsomniaOptions) { + 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 { + 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; } } diff --git a/packages/insomnia-testing/src/run/run.test.ts b/packages/insomnia-testing/src/run/run.test.ts index 4111020971..d90aa13c89 100644 --- a/packages/insomnia-testing/src/run/run.test.ts +++ b/packages/insomnia-testing/src/run/run.test.ts @@ -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>(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; } diff --git a/packages/insomnia-testing/src/run/run.ts b/packages/insomnia-testing/src/run/run.ts index a5ade15806..b68df0f38b 100644 --- a/packages/insomnia-testing/src/run/run.ts +++ b/packages/insomnia-testing/src/run/run.ts @@ -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 ( +const runInternal = async ( testSrc: string | string[], - options: InsomniaOptions, + options: InsomniaOptions, 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 => new Promise((resolve, reject) => { + extractResult: (runner: Mocha.Runner) => TReturn, +) => new Promise((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 = InsomniaOptions & { reporter?: Reporter } /** * Run a test file using Mocha */ -export const runTestsCli = async ( +export const runTestsCli = async ( testSrc: string | string[], - { reporter, ...options }: CliOptions = {}, + { reporter, ...options }: CliOptions, ) => 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 ( +export const runTests = async ( testSrc: string | string[], - options: InsomniaOptions = {}, -) => runInternal( + options: InsomniaOptions, +) => 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, ); diff --git a/packages/insomnia-testing/src/test-helpers/send-request-mock.ts b/packages/insomnia-testing/src/test-helpers/send-request-mock.ts new file mode 100644 index 0000000000..6ac2552a63 --- /dev/null +++ b/packages/insomnia-testing/src/test-helpers/send-request-mock.ts @@ -0,0 +1,17 @@ +import { mocked } from 'ts-jest/utils'; + +import { SendRequestCallback } from '../run/insomnia'; + +export const mockedSendRequest = (response?: TResp) => { + return mocked>(jest.fn()).mockResolvedValue(response || { status: 200 } as TResp); +}; + +export const mockedSendRequestMultiple = (...responses: TResp[]) => { + let mock = mocked>(jest.fn()); + + responses.forEach(response => { + mock = mock.mockResolvedValueOnce(response); + }); + + return mock; +}; diff --git a/packages/insomnia-testing/tsconfig.build.json b/packages/insomnia-testing/tsconfig.build.json index 74aeb31219..93e7f84ab9 100644 --- a/packages/insomnia-testing/tsconfig.build.json +++ b/packages/insomnia-testing/tsconfig.build.json @@ -10,7 +10,7 @@ ], "exclude": [ "**/fixtures", - "**/__mocks__", + "**/test-helpers", "**/*.test.ts" ] } \ No newline at end of file