Skip to content

Commit

Permalink
Merge pull request #453 from apollographql/mw/testing-improvements
Browse files Browse the repository at this point in the history
Testing infrastructure improvements
  • Loading branch information
martijnwalraven committed Mar 3, 2021
2 parents 33b98a0 + 58a88ef commit 933b6a2
Show file tree
Hide file tree
Showing 120 changed files with 1,651 additions and 4,081 deletions.
25 changes: 25 additions & 0 deletions .vscode/tasks.json
@@ -0,0 +1,25 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "TypeScript watch",
"type": "typescript",
"tsconfig": "tsconfig.json",
"option": "watch",
"problemMatcher": ["$tsc-watch"],
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"reveal": "never",
"revealProblems": "onProblem",
"panel": "shared",
"clear": true
},
"runOptions": {
"runOn": "folderOpen"
}
}
]
}
1 change: 1 addition & 0 deletions federation-integration-testsuite-js/src/index.ts
@@ -1 +1,2 @@
export * from './snapshotSerializers';
export * from './fixtures';
4 changes: 4 additions & 0 deletions federation-integration-testsuite-js/src/matchers/index.ts
@@ -0,0 +1,4 @@
import './toCallService';
import './toHaveBeenCalledBefore';
import './toHaveFetched';
import './toMatchAST';
@@ -1,12 +1,10 @@
import { QueryPlan } from '@apollo/gateway';
import { PlanNode } from '../../QueryPlan';
import astSerializer from '../../snapshotSerializers/astSerializer';
import queryPlanSerializer from '../../snapshotSerializers/queryPlanSerializer';
import { QueryPlan, PlanNode } from '@apollo/query-planner';
import { astSerializer, queryPlanSerializer } from '../snapshotSerializers';
const prettyFormat = require('pretty-format');

declare global {
namespace jest {
interface Matchers<R, T> {
interface Matchers<R> {
toCallService(service: string): R;
}
}
Expand Down
Expand Up @@ -3,8 +3,8 @@
export {};
declare global {
namespace jest {
interface Matchers<R, T> {
toHaveBeenCalledBefore(spy: SpyInstance): R;
interface Matchers<R> {
toHaveBeenCalledBefore(secondSpy: jest.SpyInstance): R;
}
}
}
Expand Down
@@ -1,24 +1,21 @@
import { RequestInit, Headers } from 'apollo-server-env';
type RequestInitWithJSONBody = Omit<RequestInit, 'body'> & { body?: object }

// Make this file a module
// See: https://github.com/microsoft/TypeScript/issues/17736
export {};
declare global {
namespace jest {
interface Matchers<R, T> {
toHaveFetched(spy: SpyInstance): R;
interface Matchers<R> {
toHaveFetched(requestUrl: string, requestOpts?: RequestInitWithJSONBody): R;
toHaveFetchedNth(nthCall: number, requestUrl: string, requestOpts?: RequestInitWithJSONBody): R;
}
}
}

function prepareHttpOptions(requestUrl: string, requestOpts: RequestInit): RequestInit {
const headers = new Headers();
function prepareHttpOptions(requestUrl: string, requestOpts: RequestInitWithJSONBody): RequestInit {
const headers = new Headers(requestOpts.headers);
headers.set('Content-Type', 'application/json');
if (requestOpts.headers) {
for (let name in requestOpts.headers) {
headers.set(name, requestOpts.headers[name]);
}
}

const requestHttp = {
method: 'POST',
Expand All @@ -37,7 +34,7 @@ function toHaveFetched(
this: jest.MatcherUtils,
fetch: jest.SpyInstance,
requestUrl: string,
requestOpts: RequestInit
requestOpts: RequestInitWithJSONBody = {}
): { message(): string; pass: boolean } {
const httpOptions = prepareHttpOptions(requestUrl, requestOpts);
let pass = false;
Expand All @@ -60,7 +57,7 @@ function toHaveFetchedNth(
fetch: jest.SpyInstance,
nthCall: number,
requestUrl: string,
requestOpts: RequestInit
requestOpts: RequestInitWithJSONBody = {}
): { message(): string; pass: boolean } {
const httpOptions = prepareHttpOptions(requestUrl, requestOpts);
let pass = false;
Expand Down
43 changes: 43 additions & 0 deletions federation-integration-testsuite-js/src/matchers/toMatchAST.ts
@@ -0,0 +1,43 @@
import { ASTNode } from 'graphql';
import { MatcherHintOptions } from 'jest-matcher-utils';
import { diffFormatted, indentLines, printExpectedFormatted } from './utils';

declare global {
namespace jest {
interface Matchers<R> {
toMatchAST(expected: ASTNode): R;
}
}
}

expect.extend({
toMatchAST(received: ASTNode, expected: ASTNode) {
const matcherName = 'toMatchAST';
const options: MatcherHintOptions = {
isNot: this.isNot,
promise: this.promise,
};

const pass = this.equals(received, expected);

const message = pass
? () =>
this.utils.matcherHint(matcherName, undefined, undefined, options) +
'\n\n' +
`Expected AST to not equal:\n` +
indentLines(printExpectedFormatted(expected))
: () =>
this.utils.matcherHint(matcherName, undefined, undefined, options) +
'\n\n' +
diffFormatted(expected, received, {
aAnnotation: 'Expected',
bAnnotation: 'Received',
expand: this.expand ?? true,
includeChangeCounts: true,
});
return {
message,
pass,
};
},
});
@@ -0,0 +1,43 @@
import { QueryPlan } from '@apollo/query-planner';
import { MatcherHintOptions } from 'jest-matcher-utils';
import { diffFormatted, indentLines, printExpectedFormatted } from './utils';

declare global {
namespace jest {
interface Matchers<R> {
toMatchQueryPlan(expected: QueryPlan): R;
}
}
}

expect.extend({
toMatchQueryPlan(received: QueryPlan, expected: QueryPlan) {
const matcherName = 'toMatchQueryPlan';
const options: MatcherHintOptions = {
isNot: this.isNot,
promise: this.promise,
};

const pass = this.equals(received, expected);

const message = pass
? () =>
this.utils.matcherHint(matcherName, undefined, undefined, options) +
'\n\n' +
`Expected query plan to not equal:\n` +
indentLines(printExpectedFormatted(expected))
: () =>
this.utils.matcherHint(matcherName, undefined, undefined, options) +
'\n\n' +
diffFormatted(expected, received, {
aAnnotation: 'Expected',
bAnnotation: 'Received',
expand: this.expand ?? true,
includeChangeCounts: true,
});
return {
message,
pass,
};
},
});
53 changes: 53 additions & 0 deletions federation-integration-testsuite-js/src/matchers/utils.ts
@@ -0,0 +1,53 @@
import diff, { DiffOptions } from 'jest-diff';
import { EXPECTED_COLOR, RECEIVED_COLOR } from 'jest-matcher-utils';
import prettyFormat from 'pretty-format';
import {
queryPlanSerializer,
astSerializer,
typeSerializer,
} from '../snapshotSerializers';

const defaultFormatOptions: prettyFormat.OptionsReceived = {
plugins: [queryPlanSerializer, astSerializer, typeSerializer],
};

export function diffFormatted(
expected: unknown,
received: unknown,
diffOptions?: DiffOptions,
formatOptions: prettyFormat.OptionsReceived = defaultFormatOptions,
) {
const expectedString = prettyFormat(expected, formatOptions);
const receivedString = prettyFormat(received, formatOptions);

return diff(expectedString, receivedString, diffOptions);
}

export function indentLines(
text: string,
depth: number = 1,
indent: string = ' ',
) {
const indentation = indent.repeat(depth);
return text
.split('\n')
.map((line) => indentation + line)
.join('\n');
}

// The corresponding functions in `jest-matcher-utils` call their own `stringify` function,
// and that doesn't allow passing in custom pretty-format plugins.

export function printReceivedFormatted(
value: unknown,
formatOptions: prettyFormat.OptionsReceived = defaultFormatOptions,
): string {
return RECEIVED_COLOR(prettyFormat(value, formatOptions));
}

export function printExpectedFormatted(
value: unknown,
formatOptions: prettyFormat.OptionsReceived = defaultFormatOptions,
): string {
return EXPECTED_COLOR(prettyFormat(value, formatOptions));
}
@@ -0,0 +1,6 @@
export { astSerializer } from '@apollo/query-planner';
export { queryPlanSerializer } from '@apollo/query-planner';
export { default as selectionSetSerializer } from './selectionSetSerializer';
export { default as typeSerializer } from './typeSerializer';
export { default as graphqlErrorSerializer } from './graphqlErrorSerializer';

6 changes: 3 additions & 3 deletions federation-integration-testsuite-js/tsconfig.json
Expand Up @@ -3,11 +3,11 @@
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist",
"noImplicitAny": false,
"strictNullChecks": false,
"types": ["node", "jest"]
},
"include": ["src/**/*"],
"exclude": ["**/__tests__"],
"references": []
"references": [
{ "path": "../gateway-js" }
]
}
11 changes: 7 additions & 4 deletions federation-js/jest.config.js
@@ -1,5 +1,8 @@
const config = require('../jest.config.base');
const baseConfig = require('../jest.config.base');

const additionalConfig = {};

module.exports = Object.assign(Object.create(null), config, additionalConfig);
/** @typedef {import('ts-jest/dist/types')} */
/** @type {import('@jest/types').Config.InitialOptions} */
module.exports = {
...baseConfig,
setupFilesAfterEnv: ['./src/__tests__/testSetup.ts'],
};
1 change: 1 addition & 0 deletions federation-js/src/__tests__/testSetup.ts
@@ -0,0 +1 @@
import 'apollo-federation-integration-testsuite/dist/matchers';
5 changes: 3 additions & 2 deletions federation-js/src/__tests__/tsconfig.json
@@ -1,7 +1,8 @@
{
"extends": "../../../tsconfig.test.base",
"extends": "../../tsconfig.test",
"include": ["**/*"],
"references": [
{ "path": "../../" },
{ "path": "../.." },
{ "path": "../../../federation-integration-testsuite-js" },
]
}

0 comments on commit 933b6a2

Please sign in to comment.