mirror of
https://github.com/Kong/insomnia
synced 2024-11-07 22:30:15 +00:00
Unit Test Generation and Running PoC (#2232)
Co-authored-by: Opender Singh <opender.singh@konghq.com>
This commit is contained in:
parent
df2bbda240
commit
5220d34a3a
16
packages/insomnia-testing/.babelrc
Normal file
16
packages/insomnia-testing/.babelrc
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"targets": {
|
||||
"node": "10"
|
||||
}
|
||||
}
|
||||
],
|
||||
"@babel/preset-flow"
|
||||
],
|
||||
"plugins": [
|
||||
["@babel/plugin-proposal-optional-chaining"]
|
||||
]
|
||||
}
|
16
packages/insomnia-testing/.flowconfig
Normal file
16
packages/insomnia-testing/.flowconfig
Normal file
@ -0,0 +1,16 @@
|
||||
[ignore]
|
||||
.*/node_modules/.*
|
||||
.*/__fixtures__/.*
|
||||
./.cache
|
||||
./dist
|
||||
|
||||
[include]
|
||||
|
||||
[libs]
|
||||
./flow-typed
|
||||
./types
|
||||
|
||||
[options]
|
||||
esproposal.optional_chaining=enable
|
||||
|
||||
[lints]
|
1
packages/insomnia-testing/.gitignore
vendored
Normal file
1
packages/insomnia-testing/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
dist
|
7
packages/insomnia-testing/.npmignore
Normal file
7
packages/insomnia-testing/.npmignore
Normal file
@ -0,0 +1,7 @@
|
||||
# Ignore everything by default
|
||||
# NOTE: NPM publish will never ignore package.json, package-lock.json, README, LICENSE, CHANGELOG
|
||||
# https://npm.github.io/publishing-pkgs-docs/publishing/the-npmignore-file.html
|
||||
*
|
||||
|
||||
# Don't ignore dist folder
|
||||
!dist/*
|
5
packages/insomnia-testing/flow-typed/axios.js
vendored
Normal file
5
packages/insomnia-testing/flow-typed/axios.js
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
// @flow
|
||||
|
||||
declare module 'axios' {
|
||||
declare module.exports: *;
|
||||
}
|
5
packages/insomnia-testing/flow-typed/chai.js
vendored
Normal file
5
packages/insomnia-testing/flow-typed/chai.js
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
// @flow
|
||||
|
||||
declare module 'chai' {
|
||||
declare module.exports: *;
|
||||
}
|
1186
packages/insomnia-testing/flow-typed/jest.js
vendored
Normal file
1186
packages/insomnia-testing/flow-typed/jest.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
19
packages/insomnia-testing/flow-typed/mocha.js
vendored
Normal file
19
packages/insomnia-testing/flow-typed/mocha.js
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
// @flow
|
||||
|
||||
declare class Mocha {
|
||||
constructor(options?: { global?: Array<string> }): Mocha;
|
||||
static Runner: {
|
||||
constants: Object;
|
||||
};
|
||||
static reporters: {
|
||||
Base: Object;
|
||||
JSON: Object;
|
||||
};
|
||||
reporter(reporter: Object, options: Object): void;
|
||||
addFile(filename: string): void;
|
||||
run(callback: (failures: number) => void): Object;
|
||||
}
|
||||
|
||||
declare module 'mocha' {
|
||||
declare module.exports: typeof Mocha;
|
||||
}
|
3
packages/insomnia-testing/index.js
Normal file
3
packages/insomnia-testing/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
// @flow
|
||||
export { generate } from './src/generate';
|
||||
export { runTests } from './src/run';
|
9589
packages/insomnia-testing/package-lock.json
generated
Normal file
9589
packages/insomnia-testing/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
34
packages/insomnia-testing/package.json
Normal file
34
packages/insomnia-testing/package.json
Normal file
@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "insomnia-testing",
|
||||
"version": "2.2.6",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"typecheck": "flow check",
|
||||
"test": "jest",
|
||||
"build": "webpack --config webpack.config.js --display errors-only",
|
||||
"bootstrap": "npm run build",
|
||||
"prepublish": "npm run build"
|
||||
},
|
||||
"jest": {
|
||||
"testEnvironment": "node",
|
||||
"testMatch": [
|
||||
"**/__tests__/**/*.test.js"
|
||||
],
|
||||
"verbose": false,
|
||||
"resetMocks": true,
|
||||
"resetModules": true
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.9.0",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.9.0",
|
||||
"husky": "^3.1.0",
|
||||
"jest": "^26.0.1",
|
||||
"webpack": "^4.42.1",
|
||||
"webpack-cli": "^3.3.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.19.2",
|
||||
"chai": "^4.2.0",
|
||||
"mocha": "^6.2.3"
|
||||
}
|
||||
}
|
27
packages/insomnia-testing/src/__mocks__/axios.js
Normal file
27
packages/insomnia-testing/src/__mocks__/axios.js
Normal file
@ -0,0 +1,27 @@
|
||||
const axios = jest.genMockFromModule('axios');
|
||||
|
||||
const mockResponses = {};
|
||||
|
||||
function key(method, url) {
|
||||
return `${method.toLowerCase()}:${url.toLowerCase()}`;
|
||||
}
|
||||
|
||||
axios.__setResponse = (method, url, resp) => {
|
||||
mockResponses[key(method, url)] = resp;
|
||||
};
|
||||
|
||||
axios.request = async function({ method, url }) {
|
||||
const k = key(method, url);
|
||||
const resp = mockResponses[k];
|
||||
if (!resp) {
|
||||
throw new Error(
|
||||
`Could not find mock axios response for ${k}. Options are [${Object.keys(mockResponses).join(
|
||||
', ',
|
||||
)}]`,
|
||||
);
|
||||
}
|
||||
|
||||
return Promise.resolve(resp);
|
||||
};
|
||||
|
||||
module.exports = axios;
|
100
packages/insomnia-testing/src/__tests__/integration.test.js
Normal file
100
packages/insomnia-testing/src/__tests__/integration.test.js
Normal file
@ -0,0 +1,100 @@
|
||||
// @flow
|
||||
import axios from 'axios';
|
||||
import type { TestSuite } from '../generate';
|
||||
import { generateToFile } from '../generate';
|
||||
import { runTests } from '../run';
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
import * as config from '../../webpack.config';
|
||||
|
||||
jest.mock('axios');
|
||||
|
||||
describe('webpack config', () => {
|
||||
it('should set mocha as external', () => {
|
||||
expect(config.externals.mocha).toBe('mocha');
|
||||
});
|
||||
});
|
||||
|
||||
describe('integration', () => {
|
||||
it('generates and runs basic tests', async () => {
|
||||
const testFilename = await generateToTmpFile([
|
||||
{
|
||||
name: 'Example TestSuite',
|
||||
suites: [],
|
||||
tests: [
|
||||
{
|
||||
name: 'should return -1 when the value is not present',
|
||||
code: 'expect([1, 2, 3].indexOf(4)).toBe(-1);\nexpect(true).toBe(true);',
|
||||
},
|
||||
{
|
||||
name: 'is an empty test',
|
||||
code: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
const { stats } = await runTests(testFilename);
|
||||
|
||||
expect(stats.tests).toBe(2);
|
||||
expect(stats.failures).toBe(0);
|
||||
expect(stats.passes).toBe(2);
|
||||
});
|
||||
|
||||
it('sends an HTTP request', async () => {
|
||||
axios.__setResponse('GET', '200.insomnia.rest', {
|
||||
status: 200,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
data: { foo: 'bar' },
|
||||
});
|
||||
|
||||
axios.__setResponse('GET', '301.insomnia.rest', {
|
||||
status: 301,
|
||||
headers: { location: '/blog' },
|
||||
});
|
||||
|
||||
const testFilename = await generateToTmpFile([
|
||||
{
|
||||
name: 'Example TestSuite',
|
||||
suites: [],
|
||||
tests: [
|
||||
{
|
||||
name: 'Tests sending a request',
|
||||
code: [
|
||||
`const resp = await insomnia.send({ url: '200.insomnia.rest' });`,
|
||||
`expect(resp.status).toBe(200);`,
|
||||
`expect(resp.headers['content-type']).toBe('application/json');`,
|
||||
`expect(resp.data.foo).toBe('bar');`,
|
||||
].join('\n'),
|
||||
},
|
||||
{
|
||||
name: 'Tests referencing request by ID',
|
||||
code: [
|
||||
`const resp = await insomnia.send('req_123');`,
|
||||
`expect(resp.status).toBe(301);`,
|
||||
].join('\n'),
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
const { stats } = await runTests(testFilename, {
|
||||
requests: {
|
||||
req_123: {
|
||||
url: '301.insomnia.rest',
|
||||
method: 'get',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(stats.tests).toBe(2);
|
||||
expect(stats.failures).toBe(0);
|
||||
expect(stats.passes).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
export async function generateToTmpFile(suites: Array<TestSuite>): Promise<string> {
|
||||
const p = path.join(os.tmpdir(), `${Math.random()}.test.js`);
|
||||
await generateToFile(p, suites);
|
||||
return p;
|
||||
}
|
@ -0,0 +1 @@
|
||||
[]
|
@ -0,0 +1,6 @@
|
||||
[
|
||||
{
|
||||
"name": "Example Suite",
|
||||
"tests": []
|
||||
}
|
||||
]
|
@ -0,0 +1,2 @@
|
||||
describe('Example Suite', () => {
|
||||
});
|
@ -0,0 +1,16 @@
|
||||
[
|
||||
{
|
||||
"name": "Example Suite",
|
||||
"tests": [
|
||||
{
|
||||
"name": "should return -1 when the value is not present",
|
||||
"code": "expect([1, 2, 3].indexOf(4)).toBe(-1);\nexpect(true).toBe(true);"
|
||||
},
|
||||
{
|
||||
"name": "is an empty test",
|
||||
"code": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -0,0 +1,9 @@
|
||||
describe('Example Suite', () => {
|
||||
it('should return -1 when the value is not present', async () => {
|
||||
expect([1, 2, 3].indexOf(4)).toBe(-1);
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
|
||||
it('is an empty test', async () => {
|
||||
});
|
||||
});
|
@ -0,0 +1,21 @@
|
||||
[
|
||||
{
|
||||
"name": "Parent Suite",
|
||||
"suites": [
|
||||
{
|
||||
"name": "Nested Suite",
|
||||
"tests": [
|
||||
{
|
||||
"name": "should return -1 when the value is not present",
|
||||
"code": "expect([1, 2, 3].indexOf(4)).toBe(-1);\nexpect(true).toBe(true);"
|
||||
},
|
||||
{
|
||||
"name": "is an empty test",
|
||||
"code": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -0,0 +1,11 @@
|
||||
describe('Parent Suite', () => {
|
||||
describe('Nested Suite', () => {
|
||||
it('should return -1 when the value is not present', async () => {
|
||||
expect([1, 2, 3].indexOf(4)).toBe(-1);
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
|
||||
it('is an empty test', async () => {
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,36 @@
|
||||
[
|
||||
{
|
||||
"name": "Parent Suite",
|
||||
"tests": [
|
||||
{
|
||||
"name": "should return -1 when the value is not present",
|
||||
"code": "expect([1, 2, 3].indexOf(4)).toBe(-1);\nexpect(true).toBe(true);"
|
||||
},
|
||||
{
|
||||
"name": "is an empty test",
|
||||
"code": ""
|
||||
}
|
||||
],
|
||||
"suites": [
|
||||
{
|
||||
"name": "Nested Suite",
|
||||
"suites": [
|
||||
{
|
||||
"name": "Nested Again Suite",
|
||||
"tests": [
|
||||
{
|
||||
"name": "should return -1 when the value is not present",
|
||||
"code": "expect([1, 2, 3].indexOf(4)).toBe(-1);\nexpect(true).toBe(true);"
|
||||
},
|
||||
{
|
||||
"name": "should be true",
|
||||
"code": "expect(true).toBe(true);"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -0,0 +1,22 @@
|
||||
describe('Parent Suite', () => {
|
||||
describe('Nested Suite', () => {
|
||||
describe('Nested Again Suite', () => {
|
||||
it('should return -1 when the value is not present', async () => {
|
||||
expect([1, 2, 3].indexOf(4)).toBe(-1);
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
|
||||
it('should be true', async () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should return -1 when the value is not present', async () => {
|
||||
expect([1, 2, 3].indexOf(4)).toBe(-1);
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
|
||||
it('is an empty test', async () => {
|
||||
});
|
||||
});
|
@ -0,0 +1,36 @@
|
||||
// @flow
|
||||
import { generate } from '../index';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const fixturesPath = path.join(__dirname, '../__fixtures__');
|
||||
const fixtures = fs.readdirSync(fixturesPath);
|
||||
|
||||
describe('fixtures', () => {
|
||||
for (const input of fixtures) {
|
||||
if (input.match(/\.output\.js$/)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const prefix = input.replace(/\.input\.json$/, '');
|
||||
const output = `${prefix}.output.js`;
|
||||
|
||||
if (prefix.startsWith('skip')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
it(`Generate ${input}`, async () => {
|
||||
expect(typeof input).toBe('string');
|
||||
expect(typeof output).toBe('string');
|
||||
|
||||
const inputContents = fs.readFileSync(path.join(fixturesPath, input), 'utf8');
|
||||
const outputContents = fs.readFileSync(path.join(fixturesPath, output), 'utf8');
|
||||
|
||||
expect(typeof inputContents).toBe('string');
|
||||
expect(typeof outputContents).toBe('string');
|
||||
|
||||
const expected = generate(JSON.parse(inputContents));
|
||||
expect(expected.trim()).toBe(outputContents.trim());
|
||||
});
|
||||
}
|
||||
});
|
@ -0,0 +1,31 @@
|
||||
// @flow
|
||||
|
||||
import { escapeJsStr, indent } from '../util';
|
||||
|
||||
describe('util', () => {
|
||||
describe('indent()', () => {
|
||||
it('skips indent on <= 0', () => {
|
||||
expect(indent(0, 'hello')).toBe('hello');
|
||||
expect(indent(-1, 'hello')).toBe('hello');
|
||||
});
|
||||
|
||||
it('indents single lines', () => {
|
||||
expect(indent(1, 'hello')).toBe(' hello');
|
||||
expect(indent(3, 'hello')).toBe(' hello');
|
||||
});
|
||||
|
||||
it('indents multi-line blocks', () => {
|
||||
const text = `function greet() {\n console.log('Hello World!');\n}`;
|
||||
expect(indent(1, text)).toBe(` function greet() {\n console.log('Hello World!');\n }`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('escapeJsStr()', () => {
|
||||
it('does not escape something without quotes', () => {
|
||||
expect(escapeJsStr('Hello World')).toBe('Hello World');
|
||||
});
|
||||
it('escapes something with quotes', () => {
|
||||
expect(escapeJsStr(`"Hello" 'World'`)).toBe(`"Hello" \\'World\\'`);
|
||||
});
|
||||
});
|
||||
});
|
77
packages/insomnia-testing/src/generate/index.js
Normal file
77
packages/insomnia-testing/src/generate/index.js
Normal file
@ -0,0 +1,77 @@
|
||||
// @flow
|
||||
|
||||
import { escapeJsStr, indent } from './util';
|
||||
import fs from 'fs';
|
||||
|
||||
export type Test = {
|
||||
name: string,
|
||||
code: string,
|
||||
};
|
||||
|
||||
export type TestSuite = {
|
||||
name: string,
|
||||
suites: Array<TestSuite>,
|
||||
tests?: Array<Test>,
|
||||
};
|
||||
|
||||
export function generate(suites: Array<TestSuite>): string {
|
||||
const lines = [];
|
||||
|
||||
for (const s of suites || []) {
|
||||
lines.push(...generateSuiteLines(0, s));
|
||||
}
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
export async function generateToFile(filepath: string, suites: Array<TestSuite>): Promise<void> {
|
||||
const js = generate(suites);
|
||||
return fs.promises.writeFile(filepath, js);
|
||||
}
|
||||
|
||||
function generateSuiteLines(n: number, suite: ?TestSuite): Array<string> {
|
||||
if (!suite) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const lines = [];
|
||||
lines.push(indent(n, `describe('${escapeJsStr(suite.name)}', () => {`));
|
||||
|
||||
const suites = suite.suites || [];
|
||||
for (let i = 0; i < suites.length; i++) {
|
||||
if (i !== 0) {
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
lines.push(...generateSuiteLines(n + 1, suites[i]));
|
||||
}
|
||||
|
||||
const tests = suite.tests || [];
|
||||
for (let i = 0; i < tests.length; i++) {
|
||||
// Add blank like if
|
||||
// - it's the first test
|
||||
// - we've outputted suites above
|
||||
if (suites.length > 0 || i !== 0) {
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
lines.push(...generateTestLines(n + 1, tests[i]));
|
||||
}
|
||||
|
||||
lines.push(indent(n, `});`));
|
||||
return lines;
|
||||
}
|
||||
|
||||
function generateTestLines(n: number, test: ?Test): Array<string> {
|
||||
if (!test) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const lines = [];
|
||||
|
||||
lines.push(indent(n, `it('${escapeJsStr(test.name)}', async () => {`));
|
||||
test.code && lines.push(indent(n + 1, test.code));
|
||||
lines.push(indent(n, `});`));
|
||||
|
||||
return lines;
|
||||
}
|
17
packages/insomnia-testing/src/generate/util.js
Normal file
17
packages/insomnia-testing/src/generate/util.js
Normal file
@ -0,0 +1,17 @@
|
||||
// @flow
|
||||
|
||||
export function escapeJsStr(s: string): string {
|
||||
return s.replace(/'/g, `\\'`);
|
||||
}
|
||||
|
||||
export function indent(level: number, code: string, tab: string = ' '): string {
|
||||
if (!level || level < 0) {
|
||||
return code;
|
||||
}
|
||||
|
||||
const prefix = new Array(level + 1).join(' ');
|
||||
return code
|
||||
.split('\n')
|
||||
.map(line => prefix + line)
|
||||
.join('\n');
|
||||
}
|
41
packages/insomnia-testing/src/run/__tests__/index.test.js
Normal file
41
packages/insomnia-testing/src/run/__tests__/index.test.js
Normal file
@ -0,0 +1,41 @@
|
||||
// @flow
|
||||
|
||||
import { runTests } from '../index';
|
||||
import os from 'os';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const exampleTest = `
|
||||
const assert = require('assert');
|
||||
describe('Example', () => {
|
||||
it('should be true', async () => assert.equal(true, true));
|
||||
it('should fail', async () => assert.equal(true, false));
|
||||
});
|
||||
`;
|
||||
|
||||
describe('run', () => {
|
||||
it('runs a mocha suite', async () => {
|
||||
const testPath = writeToTmp(exampleTest);
|
||||
|
||||
const { stats } = await runTests(testPath);
|
||||
expect(stats.passes).toBe(1);
|
||||
expect(stats.tests).toBe(2);
|
||||
expect(stats.failures).toBe(1);
|
||||
});
|
||||
|
||||
it('works on multiple files', async () => {
|
||||
const testPath1 = writeToTmp(exampleTest);
|
||||
const testPath2 = writeToTmp(exampleTest);
|
||||
|
||||
const { stats } = await runTests([testPath1, testPath2]);
|
||||
expect(stats.passes).toBe(2);
|
||||
expect(stats.tests).toBe(4);
|
||||
expect(stats.failures).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
function writeToTmp(contents: string): string {
|
||||
const tmpPath = path.join(os.tmpdir(), `${Math.random()}.test.js`);
|
||||
fs.writeFileSync(tmpPath, contents);
|
||||
return tmpPath;
|
||||
}
|
74
packages/insomnia-testing/src/run/index.js
Normal file
74
packages/insomnia-testing/src/run/index.js
Normal file
@ -0,0 +1,74 @@
|
||||
// @flow
|
||||
|
||||
import Mocha from 'mocha';
|
||||
import { JavaScriptReporter } from './javaScriptReporter';
|
||||
import Insomnia from './insomnia';
|
||||
import type { Request } from './insomnia';
|
||||
|
||||
type TestErr = {
|
||||
generatedMessage: boolean,
|
||||
name: string,
|
||||
code: string,
|
||||
actual: string,
|
||||
expected: string,
|
||||
operator: string,
|
||||
};
|
||||
|
||||
type TestResult = {
|
||||
title: string,
|
||||
fullTitle: string,
|
||||
file: string,
|
||||
duration: number,
|
||||
currentRetry: number,
|
||||
err: TestErr | {},
|
||||
};
|
||||
|
||||
type TestResults = {
|
||||
stats: {
|
||||
suites: number,
|
||||
tests: number,
|
||||
passes: number,
|
||||
pending: number,
|
||||
failures: number,
|
||||
start: Date,
|
||||
end: Date,
|
||||
duration: number,
|
||||
},
|
||||
tests: Array<TestResult>,
|
||||
pending: Array<TestResult>,
|
||||
failures: Array<TestResult>,
|
||||
passes: Array<TestResult>,
|
||||
};
|
||||
|
||||
/**
|
||||
* Run a test file using Mocha
|
||||
*/
|
||||
export async function runTests(
|
||||
filename: string | Array<string>,
|
||||
options: { requests?: { [string]: Request } } = {},
|
||||
): Promise<TestResults> {
|
||||
return new Promise(resolve => {
|
||||
// Add global `insomnia` helper.
|
||||
// This is the only way to add new globals to the Mocha environment as far
|
||||
// as I can tell
|
||||
global.insomnia = new Insomnia(options.requests);
|
||||
|
||||
const mocha = new Mocha({
|
||||
global: ['insomnia'],
|
||||
});
|
||||
|
||||
mocha.reporter(JavaScriptReporter);
|
||||
|
||||
const filenames = Array.isArray(filename) ? filename : [filename];
|
||||
for (const f of filenames) {
|
||||
mocha.addFile(f);
|
||||
}
|
||||
|
||||
const runner = mocha.run(() => {
|
||||
resolve(runner.testResults);
|
||||
|
||||
// Remove global since we don't need it anymore
|
||||
delete global.insomnia;
|
||||
});
|
||||
});
|
||||
}
|
71
packages/insomnia-testing/src/run/insomnia.js
Normal file
71
packages/insomnia-testing/src/run/insomnia.js
Normal file
@ -0,0 +1,71 @@
|
||||
// @flow
|
||||
import axios from 'axios';
|
||||
|
||||
export type Request = {
|
||||
url?: string,
|
||||
method?: string,
|
||||
headers?: {
|
||||
[string]: string,
|
||||
},
|
||||
};
|
||||
|
||||
export type Response = {
|
||||
status: number,
|
||||
statusText: string,
|
||||
data: Object,
|
||||
headers: {
|
||||
[string]: string,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* An instance of Insomnia will be exposed as a global variable during
|
||||
* tests, and will provide a bunch of utility functions for sending
|
||||
* requests, etc.
|
||||
*/
|
||||
export default class Insomnia {
|
||||
requests: { [string]: $Shape<Request> };
|
||||
|
||||
/**
|
||||
* @param requests - map of ID -> Request to be used when referencing requests by Id
|
||||
*/
|
||||
constructor(requests?: { [string]: $Shape<Request> }) {
|
||||
this.requests = requests || {};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param req - raw request object or an ID to reference a request
|
||||
* @returns {Promise<{headers: *, data: *, statusText: (string|string), status: *}>}
|
||||
*/
|
||||
async send(req: string | Request): Promise<Response> {
|
||||
if (typeof req === 'string' && !this.requests.hasOwnProperty(req)) {
|
||||
throw new Error(`Failed to find request by ID ${req}`);
|
||||
}
|
||||
|
||||
if (typeof req === 'string' && this.requests.hasOwnProperty(req)) {
|
||||
req = this.requests[req];
|
||||
}
|
||||
|
||||
const options = {
|
||||
url: req.url || '',
|
||||
method: req.method || 'GET',
|
||||
headers: req.headers || {},
|
||||
|
||||
// Don't follow redirects,
|
||||
maxRedirects: 0,
|
||||
|
||||
// Don't throw errors on status != 200
|
||||
validateStatus: status => true,
|
||||
};
|
||||
|
||||
const resp = await axios.request(options);
|
||||
|
||||
return {
|
||||
status: resp.status,
|
||||
statusText: resp.statusText,
|
||||
data: resp.data,
|
||||
headers: resp.headers,
|
||||
};
|
||||
}
|
||||
}
|
111
packages/insomnia-testing/src/run/javaScriptReporter.js
Normal file
111
packages/insomnia-testing/src/run/javaScriptReporter.js
Normal file
@ -0,0 +1,111 @@
|
||||
/**
|
||||
* NOTE: This is a straight copy of the default Mocha JSON reporter, except
|
||||
* stdout logging is removed.
|
||||
*
|
||||
* https://github.com/mochajs/mocha/blob/9d4a8ec2d22ee154aecb1f8eeb25af8e6309faa8/lib/reporters/json.js
|
||||
*/
|
||||
import Mocha from 'mocha';
|
||||
|
||||
export function JavaScriptReporter(runner, options) {
|
||||
Mocha.reporters.Base.call(this, runner, options);
|
||||
|
||||
const self = this;
|
||||
const tests = [];
|
||||
const pending = [];
|
||||
const failures = [];
|
||||
const passes = [];
|
||||
|
||||
runner.on(Mocha.Runner.constants.EVENT_TEST_END, function(test) {
|
||||
tests.push(test);
|
||||
});
|
||||
|
||||
runner.on(Mocha.Runner.constants.EVENT_TEST_PASS, function(test) {
|
||||
passes.push(test);
|
||||
});
|
||||
|
||||
runner.on(Mocha.Runner.constants.EVENT_TEST_FAIL, function(test) {
|
||||
failures.push(test);
|
||||
});
|
||||
|
||||
runner.on(Mocha.Runner.constants.EVENT_TEST_PENDING, function(test) {
|
||||
pending.push(test);
|
||||
});
|
||||
|
||||
runner.once(Mocha.Runner.constants.EVENT_RUN_END, function() {
|
||||
runner.testResults = {
|
||||
stats: self.stats,
|
||||
tests: tests.map(clean),
|
||||
pending: pending.map(clean),
|
||||
failures: failures.map(clean),
|
||||
passes: passes.map(clean),
|
||||
};
|
||||
|
||||
// This is the main change from the original JSONReporter
|
||||
// process.stdout.write(JSON.stringify(obj, null, 2));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a plain-object representation of `test`
|
||||
* free of cyclic properties etc.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} test
|
||||
* @return {Object}
|
||||
*/
|
||||
function clean(test) {
|
||||
let err = test.err || {};
|
||||
if (err instanceof Error) {
|
||||
err = errorJSON(err);
|
||||
}
|
||||
|
||||
return {
|
||||
title: test.title,
|
||||
fullTitle: test.fullTitle(),
|
||||
file: test.file,
|
||||
duration: test.duration,
|
||||
currentRetry: test.currentRetry(),
|
||||
err: cleanCycles(err),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces any circular references inside `obj` with '[object Object]'
|
||||
*
|
||||
* @private
|
||||
* @param {Object} obj
|
||||
* @return {Object}
|
||||
*/
|
||||
function cleanCycles(obj) {
|
||||
const cache = [];
|
||||
return JSON.parse(
|
||||
JSON.stringify(obj, function(key, value) {
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
if (cache.indexOf(value) !== -1) {
|
||||
// Instead of going in a circle, we'll print [object Object]
|
||||
return '' + value;
|
||||
}
|
||||
cache.push(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform an Error object into a JSON object.
|
||||
*
|
||||
* @private
|
||||
* @param {Error} err
|
||||
* @return {Object}
|
||||
*/
|
||||
function errorJSON(err) {
|
||||
const res = {};
|
||||
Object.getOwnPropertyNames(err).forEach(function(key) {
|
||||
res[key] = err[key];
|
||||
}, err);
|
||||
return res;
|
||||
}
|
||||
|
||||
JavaScriptReporter.description = 'single JS object';
|
32
packages/insomnia-testing/webpack.config.js
Normal file
32
packages/insomnia-testing/webpack.config.js
Normal file
@ -0,0 +1,32 @@
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
entry: { index: './index.js' },
|
||||
target: 'node',
|
||||
mode: 'production',
|
||||
devtool: 'source-map',
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: '[name].js',
|
||||
library: 'insomniatesting',
|
||||
libraryTarget: 'commonjs2',
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
externals: {
|
||||
// Don't bundle Mocha because it needs to use require() to
|
||||
// load tests. If it's bundled in the Webpack build, it will
|
||||
// try to use Webpack's require() function and fail to
|
||||
// import the test file because it lives outside the bundle.
|
||||
mocha: 'mocha',
|
||||
},
|
||||
};
|
Loading…
Reference in New Issue
Block a user