Skip to content

Commit f55ce9b

Browse files
committed
feat: add helpers for testing – testOperation(), testOperationData(), testOperationErrors(), testSDL(), testBuildSchema()
1 parent 5f7febe commit f55ce9b

File tree

2 files changed

+140
-0
lines changed

2 files changed

+140
-0
lines changed

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ export function loadSchemaComposer(module: NodeModule, opts: BuildOptions) {
1414
}
1515

1616
export { directoryToAst, astToSchema };
17+
export * from './testHelpers';

src/testHelpers.ts

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import { graphql, GraphQLSchema, ExecutionResult, GraphQLError } from 'graphql';
2+
import {
3+
SchemaComposer,
4+
Resolver,
5+
ObjectTypeComposerFieldConfigAsObjectDefinition,
6+
inspect,
7+
} from 'graphql-compose';
8+
9+
const FIELD = 'field';
10+
11+
interface RunQueryOpts {
12+
fc: ObjectTypeComposerFieldConfigAsObjectDefinition<any, any, any> | Resolver;
13+
operation: string;
14+
variables?: Record<string, any>;
15+
source?: Record<string, any>;
16+
context?: Record<string, any>;
17+
schemaComposer?: SchemaComposer<any>;
18+
}
19+
20+
export function testBuildSchema(
21+
fc: ObjectTypeComposerFieldConfigAsObjectDefinition<any, any, any> | Resolver,
22+
schemaComposer?: SchemaComposer<any>
23+
): GraphQLSchema {
24+
const sc = schemaComposer || new SchemaComposer();
25+
sc.Query.setField(FIELD, fc);
26+
return sc.buildSchema();
27+
}
28+
29+
function _getArgsForQuery(
30+
fc: ObjectTypeComposerFieldConfigAsObjectDefinition<any, any, any> | Resolver,
31+
variables: Record<string, any> = {},
32+
schemaComposer?: SchemaComposer<any>
33+
): {
34+
queryVars: string;
35+
fieldVars: string;
36+
} {
37+
const sc = schemaComposer || new SchemaComposer();
38+
sc.Query.setField(FIELD, fc);
39+
40+
const varNames = Object.keys(variables);
41+
42+
const argNames = sc.Query.getFieldArgNames(FIELD);
43+
if (argNames.length === 0 && varNames.length > 0) {
44+
throw new Error(
45+
`FieldConfig does not have any arguments. But in test you provided the following variables: ${inspect(
46+
variables
47+
)}`
48+
);
49+
}
50+
51+
varNames.forEach((varName) => {
52+
if (!argNames.includes(varName)) {
53+
throw new Error(
54+
`FieldConfig does not have '${varName}' argument. Avaliable arguments: '${argNames.join(
55+
"', '"
56+
)}'.`
57+
);
58+
}
59+
});
60+
61+
argNames.forEach((argName) => {
62+
if (sc.Query.isFieldArgNonNull(FIELD, argName)) {
63+
const val = variables[argName];
64+
if (val === null || val === undefined) {
65+
throw new Error(
66+
`FieldConfig has required argument '${argName}'. But you did not provide it in your test via variables: '${inspect(
67+
variables
68+
)}'.`
69+
);
70+
}
71+
}
72+
});
73+
74+
const queryVars = varNames
75+
.map((n) => `$${n}: ${String(sc.Query.getFieldArgType(FIELD, n))}`)
76+
.join(' ');
77+
const fieldVars = varNames.map((n) => `${n}: $${n}`).join(' ');
78+
79+
return {
80+
queryVars: queryVars ? `(${queryVars})` : '',
81+
fieldVars: fieldVars ? `(${fieldVars})` : '',
82+
};
83+
}
84+
85+
export async function testOperation(opts: RunQueryOpts): Promise<ExecutionResult<any>> {
86+
const schema = testBuildSchema(opts.fc, opts.schemaComposer);
87+
88+
const res = await graphql({
89+
schema,
90+
source: opts.operation,
91+
rootValue: opts?.source,
92+
contextValue: opts?.context,
93+
variableValues: opts?.variables,
94+
});
95+
return res;
96+
}
97+
98+
export async function testOperationData(
99+
opts: Omit<RunQueryOpts, 'operation'> & { selectionSet: string }
100+
): Promise<Record<string, any> | null> {
101+
const { selectionSet, ...restOpts } = opts;
102+
103+
const ac = _getArgsForQuery(opts.fc, opts.variables, opts.schemaComposer);
104+
const res = await testOperation({
105+
operation: `
106+
query ${ac.queryVars} {
107+
field${ac.fieldVars} ${selectionSet.trim()}
108+
}
109+
`,
110+
...restOpts,
111+
});
112+
113+
if (res.errors) {
114+
throw new Error((res?.errors?.[0] as any) || 'GraphQL Error');
115+
}
116+
117+
return res?.data?.field;
118+
}
119+
120+
export async function testOperationErrors(
121+
opts: RunQueryOpts
122+
): Promise<readonly GraphQLError[] | void> {
123+
const res = await testOperation(opts);
124+
return res?.errors;
125+
}
126+
127+
export function testSDL(opts: {
128+
fc: ObjectTypeComposerFieldConfigAsObjectDefinition<any, any, any> | Resolver;
129+
schemaComposer?: SchemaComposer<any>;
130+
deep?: boolean;
131+
}) {
132+
const sc = opts.schemaComposer || new SchemaComposer();
133+
sc.Query.setField(FIELD, opts.fc);
134+
sc.buildSchema();
135+
return sc.Query.toSDL({
136+
deep: opts.deep ?? true,
137+
omitDescriptions: true,
138+
});
139+
}

0 commit comments

Comments
 (0)