@khanacademy/wonder-blocks-testing 0.0.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -2
- package/package.json +4 -3
- package/src/gql/__tests__/gql-request-matches-mock.test.js +235 -0
- package/src/gql/__tests__/make-gql-mock-response.test.js +298 -0
- package/src/gql/__tests__/mock-gql-fetch.test.js +469 -0
- package/src/gql/__tests__/wb-data-integration.test.js +267 -0
- package/src/gql/gql-request-matches-mock.js +74 -0
- package/src/gql/make-gql-mock-response.js +124 -0
- package/src/gql/mock-gql-fetch.js +96 -0
- package/src/gql/types.js +41 -0
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import {render, screen, waitFor} from "@testing-library/react";
|
|
4
|
+
|
|
5
|
+
import {GqlRouter, useGql} from "@khanacademy/wonder-blocks-data";
|
|
6
|
+
import {RespondWith} from "../make-gql-mock-response.js";
|
|
7
|
+
import {mockGqlFetch} from "../mock-gql-fetch.js";
|
|
8
|
+
|
|
9
|
+
describe("#mockGqlFetch", () => {
|
|
10
|
+
describe("with GqlRouter and useGql", () => {
|
|
11
|
+
it("should reject when there are no mocks", async () => {
|
|
12
|
+
// Arrange
|
|
13
|
+
const mockFetch = mockGqlFetch();
|
|
14
|
+
const RenderError = () => {
|
|
15
|
+
const [result, setResult] = React.useState(null);
|
|
16
|
+
const gqlFetch = useGql();
|
|
17
|
+
React.useEffect(() => {
|
|
18
|
+
gqlFetch({
|
|
19
|
+
type: "query",
|
|
20
|
+
id: "getMyStuff",
|
|
21
|
+
}).catch((e) => {
|
|
22
|
+
setResult(e.message);
|
|
23
|
+
});
|
|
24
|
+
}, [gqlFetch]);
|
|
25
|
+
|
|
26
|
+
return <div data-test-id="result">{result}</div>;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Act
|
|
30
|
+
render(
|
|
31
|
+
<GqlRouter defaultContext={{}} fetch={mockFetch}>
|
|
32
|
+
<RenderError />
|
|
33
|
+
</GqlRouter>,
|
|
34
|
+
);
|
|
35
|
+
const result = screen.getByTestId("result");
|
|
36
|
+
|
|
37
|
+
// Assert
|
|
38
|
+
await waitFor(() =>
|
|
39
|
+
expect(result).toHaveTextContent(
|
|
40
|
+
"No matching GraphQL mock response found for request",
|
|
41
|
+
),
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("should provide data when response gives data", async () => {
|
|
46
|
+
// Arrange
|
|
47
|
+
const mockFetch = mockGqlFetch();
|
|
48
|
+
const query = {
|
|
49
|
+
type: "query",
|
|
50
|
+
id: "getMyStuff",
|
|
51
|
+
};
|
|
52
|
+
const data = {myStuff: "stuff"};
|
|
53
|
+
const RenderError = () => {
|
|
54
|
+
const [result, setResult] = React.useState(null);
|
|
55
|
+
const gqlFetch = useGql();
|
|
56
|
+
React.useEffect(() => {
|
|
57
|
+
// eslint-disable-next-line promise/catch-or-return
|
|
58
|
+
gqlFetch(query).then((r) => {
|
|
59
|
+
setResult(JSON.stringify(r ?? "(null)"));
|
|
60
|
+
return;
|
|
61
|
+
});
|
|
62
|
+
}, [gqlFetch]);
|
|
63
|
+
|
|
64
|
+
return <div data-test-id="result">{result}</div>;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// Act
|
|
68
|
+
mockFetch.mockOperation({operation: query}, RespondWith.data(data));
|
|
69
|
+
render(
|
|
70
|
+
<GqlRouter defaultContext={{}} fetch={mockFetch}>
|
|
71
|
+
<RenderError />
|
|
72
|
+
</GqlRouter>,
|
|
73
|
+
);
|
|
74
|
+
const result = screen.getByTestId("result");
|
|
75
|
+
|
|
76
|
+
// Assert
|
|
77
|
+
await waitFor(() =>
|
|
78
|
+
expect(result).toHaveTextContent(JSON.stringify(data)),
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("should provide null data if aborted response", async () => {
|
|
83
|
+
// Arrange
|
|
84
|
+
const mockFetch = mockGqlFetch();
|
|
85
|
+
const query = {
|
|
86
|
+
type: "query",
|
|
87
|
+
id: "getMyStuff",
|
|
88
|
+
};
|
|
89
|
+
const RenderError = () => {
|
|
90
|
+
const [result, setResult] = React.useState(null);
|
|
91
|
+
const gqlFetch = useGql();
|
|
92
|
+
React.useEffect(() => {
|
|
93
|
+
// eslint-disable-next-line promise/catch-or-return
|
|
94
|
+
gqlFetch(query).then((r) => {
|
|
95
|
+
setResult(JSON.stringify(r ?? "(null)"));
|
|
96
|
+
return;
|
|
97
|
+
});
|
|
98
|
+
}, [gqlFetch]);
|
|
99
|
+
|
|
100
|
+
return <div data-test-id="result">{result}</div>;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Act
|
|
104
|
+
mockFetch.mockOperation(
|
|
105
|
+
{operation: query},
|
|
106
|
+
RespondWith.abortedRequest(),
|
|
107
|
+
);
|
|
108
|
+
render(
|
|
109
|
+
<GqlRouter defaultContext={{}} fetch={mockFetch}>
|
|
110
|
+
<RenderError />
|
|
111
|
+
</GqlRouter>,
|
|
112
|
+
);
|
|
113
|
+
const result = screen.getByTestId("result");
|
|
114
|
+
|
|
115
|
+
// Assert
|
|
116
|
+
await waitFor(() => expect(result).toHaveTextContent('"(null)"'));
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("should reject when request gives failed error code", async () => {
|
|
120
|
+
// Arrange
|
|
121
|
+
const mockFetch = mockGqlFetch();
|
|
122
|
+
const query = {
|
|
123
|
+
type: "query",
|
|
124
|
+
id: "getMyStuff",
|
|
125
|
+
};
|
|
126
|
+
const RenderError = () => {
|
|
127
|
+
const [result, setResult] = React.useState(null);
|
|
128
|
+
const gqlFetch = useGql();
|
|
129
|
+
React.useEffect(() => {
|
|
130
|
+
// eslint-disable-next-line promise/catch-or-return
|
|
131
|
+
gqlFetch(query).catch((e) => {
|
|
132
|
+
setResult(e.message);
|
|
133
|
+
});
|
|
134
|
+
}, [gqlFetch]);
|
|
135
|
+
|
|
136
|
+
return <div data-test-id="result">{result}</div>;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// Act
|
|
140
|
+
mockFetch.mockOperation(
|
|
141
|
+
{operation: query},
|
|
142
|
+
RespondWith.errorStatusCode(404),
|
|
143
|
+
);
|
|
144
|
+
render(
|
|
145
|
+
<GqlRouter defaultContext={{}} fetch={mockFetch}>
|
|
146
|
+
<RenderError />
|
|
147
|
+
</GqlRouter>,
|
|
148
|
+
);
|
|
149
|
+
const result = screen.getByTestId("result");
|
|
150
|
+
|
|
151
|
+
// Assert
|
|
152
|
+
await waitFor(() =>
|
|
153
|
+
expect(result).toHaveTextContent("Response unsuccessful"),
|
|
154
|
+
);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("should reject with a useful error when there are no matching mocks", async () => {
|
|
159
|
+
// Arrange
|
|
160
|
+
const mockFetch = mockGqlFetch();
|
|
161
|
+
|
|
162
|
+
// Act
|
|
163
|
+
const result = mockFetch(
|
|
164
|
+
{
|
|
165
|
+
type: "query",
|
|
166
|
+
id: "getMyStuff",
|
|
167
|
+
},
|
|
168
|
+
{a: "variable"},
|
|
169
|
+
{my: "context"},
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
// Assert
|
|
173
|
+
await expect(result).rejects.toThrowErrorMatchingInlineSnapshot(`
|
|
174
|
+
"No matching GraphQL mock response found for request:
|
|
175
|
+
Operation: query getMyStuff
|
|
176
|
+
Variables: {
|
|
177
|
+
\\"a\\": \\"variable\\"
|
|
178
|
+
}
|
|
179
|
+
Context: {
|
|
180
|
+
\\"my\\": \\"context\\"
|
|
181
|
+
}"
|
|
182
|
+
`);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
describe("mockOperation", () => {
|
|
186
|
+
it("should match a similar operation when no variables or context in mock", async () => {
|
|
187
|
+
// Arrange
|
|
188
|
+
const mockFetch = mockGqlFetch();
|
|
189
|
+
const operation = {
|
|
190
|
+
type: "query",
|
|
191
|
+
id: "getMyStuff",
|
|
192
|
+
};
|
|
193
|
+
const data = {
|
|
194
|
+
myStuff: "stuff",
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// Act
|
|
198
|
+
mockFetch.mockOperation({operation}, RespondWith.data(data));
|
|
199
|
+
const result = mockFetch(
|
|
200
|
+
operation,
|
|
201
|
+
{a: "variable"},
|
|
202
|
+
{my: "context"},
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// Assert
|
|
206
|
+
await expect(result).resolves.toBeDefined();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("should not match a different operation when no variables or context in mock", async () => {
|
|
210
|
+
// Arrange
|
|
211
|
+
const mockFetch = mockGqlFetch();
|
|
212
|
+
const operation = {
|
|
213
|
+
type: "query",
|
|
214
|
+
id: "getMyStuff",
|
|
215
|
+
};
|
|
216
|
+
const data = {
|
|
217
|
+
myStuff: "stuff",
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// Act
|
|
221
|
+
mockFetch.mockOperation({operation}, RespondWith.data(data));
|
|
222
|
+
const result = mockFetch(
|
|
223
|
+
{type: "mutation", id: "putMyStuff"},
|
|
224
|
+
{a: "variable"},
|
|
225
|
+
{my: "context"},
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
// Assert
|
|
229
|
+
await expect(result).rejects.toThrowError();
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("should match a similar operation when variables also match and no context in mock", async () => {
|
|
233
|
+
// Arrange
|
|
234
|
+
const mockFetch = mockGqlFetch();
|
|
235
|
+
const operation = {
|
|
236
|
+
type: "query",
|
|
237
|
+
id: "getMyStuff",
|
|
238
|
+
};
|
|
239
|
+
const variables = {
|
|
240
|
+
a: "variable",
|
|
241
|
+
};
|
|
242
|
+
const data = {
|
|
243
|
+
myStuff: "stuff",
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
// Act
|
|
247
|
+
mockFetch.mockOperation(
|
|
248
|
+
{operation, variables},
|
|
249
|
+
RespondWith.data(data),
|
|
250
|
+
);
|
|
251
|
+
const result = mockFetch(operation, variables, {my: "context"});
|
|
252
|
+
|
|
253
|
+
// Assert
|
|
254
|
+
await expect(result).resolves.toBeDefined();
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("should not match a similar operation when variables do not match and no context in mock", async () => {
|
|
258
|
+
// Arrange
|
|
259
|
+
const mockFetch = mockGqlFetch();
|
|
260
|
+
const operation = {
|
|
261
|
+
type: "query",
|
|
262
|
+
id: "getMyStuff",
|
|
263
|
+
};
|
|
264
|
+
const variables = {
|
|
265
|
+
a: "variable",
|
|
266
|
+
};
|
|
267
|
+
const data = {
|
|
268
|
+
myStuff: "stuff",
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
// Act
|
|
272
|
+
mockFetch.mockOperation(
|
|
273
|
+
{operation, variables},
|
|
274
|
+
RespondWith.data(data),
|
|
275
|
+
);
|
|
276
|
+
const result = mockFetch(
|
|
277
|
+
operation,
|
|
278
|
+
{b: "different variable"},
|
|
279
|
+
{my: "context"},
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
// Assert
|
|
283
|
+
await expect(result).rejects.toThrowError();
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it("should match any similar operation when context also matches and no variables in mock", async () => {
|
|
287
|
+
// Arrange
|
|
288
|
+
const mockFetch = mockGqlFetch();
|
|
289
|
+
const operation = {
|
|
290
|
+
type: "query",
|
|
291
|
+
id: "getMyStuff",
|
|
292
|
+
};
|
|
293
|
+
const context = {
|
|
294
|
+
my: "context",
|
|
295
|
+
};
|
|
296
|
+
const data = {
|
|
297
|
+
myStuff: "stuff",
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
// Act
|
|
301
|
+
mockFetch.mockOperation(
|
|
302
|
+
{operation, context},
|
|
303
|
+
RespondWith.data(data),
|
|
304
|
+
);
|
|
305
|
+
const result = mockFetch(operation, {a: "variable"}, context);
|
|
306
|
+
|
|
307
|
+
// Assert
|
|
308
|
+
await expect(result).resolves.toBeDefined();
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it("should not match any similar operation when context does not match and no variables in mock", async () => {
|
|
312
|
+
// Arrange
|
|
313
|
+
const mockFetch = mockGqlFetch();
|
|
314
|
+
const operation = {
|
|
315
|
+
type: "query",
|
|
316
|
+
id: "getMyStuff",
|
|
317
|
+
};
|
|
318
|
+
const context = {
|
|
319
|
+
my: "context",
|
|
320
|
+
};
|
|
321
|
+
const data = {
|
|
322
|
+
myStuff: "stuff",
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
// Act
|
|
326
|
+
mockFetch.mockOperation(
|
|
327
|
+
{operation, context},
|
|
328
|
+
RespondWith.data(data),
|
|
329
|
+
);
|
|
330
|
+
const result = mockFetch(
|
|
331
|
+
operation,
|
|
332
|
+
{a: "variable"},
|
|
333
|
+
{different: "context"},
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
// Assert
|
|
337
|
+
await expect(result).rejects.toThrowError();
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it("should match operation when variables and context match the mock", async () => {
|
|
341
|
+
// Arrange
|
|
342
|
+
const mockFetch = mockGqlFetch();
|
|
343
|
+
const operation = {
|
|
344
|
+
type: "query",
|
|
345
|
+
id: "getMyStuff",
|
|
346
|
+
};
|
|
347
|
+
const variables = {
|
|
348
|
+
a: "variable",
|
|
349
|
+
};
|
|
350
|
+
const context = {
|
|
351
|
+
my: "context",
|
|
352
|
+
};
|
|
353
|
+
const data = {
|
|
354
|
+
myStuff: "stuff",
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
// Act
|
|
358
|
+
mockFetch.mockOperation(
|
|
359
|
+
{operation, variables, context},
|
|
360
|
+
RespondWith.data(data),
|
|
361
|
+
);
|
|
362
|
+
const result = mockFetch(operation, variables, context);
|
|
363
|
+
|
|
364
|
+
// Assert
|
|
365
|
+
await expect(result).resolves.toBeDefined();
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it("should resolve to provide the matching data", async () => {
|
|
369
|
+
// Arrange
|
|
370
|
+
const mockFetch = mockGqlFetch();
|
|
371
|
+
const operation = {
|
|
372
|
+
type: "query",
|
|
373
|
+
id: "getMyStuff",
|
|
374
|
+
};
|
|
375
|
+
const variables = {
|
|
376
|
+
a: "variable",
|
|
377
|
+
};
|
|
378
|
+
const context = {
|
|
379
|
+
my: "context",
|
|
380
|
+
};
|
|
381
|
+
const data = {
|
|
382
|
+
myStuff: "stuff",
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
// Act
|
|
386
|
+
mockFetch.mockOperation(
|
|
387
|
+
{operation, variables, context},
|
|
388
|
+
RespondWith.data(data),
|
|
389
|
+
);
|
|
390
|
+
const response = await mockFetch(operation, variables, context);
|
|
391
|
+
const result = await response.text();
|
|
392
|
+
|
|
393
|
+
// Assert
|
|
394
|
+
expect(result).toBe(JSON.stringify({data}));
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
it("should match all call matches", async () => {
|
|
398
|
+
// Arrange
|
|
399
|
+
const mockFetch = mockGqlFetch();
|
|
400
|
+
const operation = {
|
|
401
|
+
type: "query",
|
|
402
|
+
id: "getMyStuff",
|
|
403
|
+
};
|
|
404
|
+
const data = {
|
|
405
|
+
myStuff: "stuff",
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
// Act
|
|
409
|
+
mockFetch.mockOperation({operation}, RespondWith.data(data));
|
|
410
|
+
const result = Promise.all([
|
|
411
|
+
mockFetch(operation, {a: "variable"}, {my: "context"}),
|
|
412
|
+
mockFetch(operation, {b: "variable"}, {another: "context"}),
|
|
413
|
+
]);
|
|
414
|
+
|
|
415
|
+
// Assert
|
|
416
|
+
await expect(result).resolves.toStrictEqual([
|
|
417
|
+
expect.any(Object),
|
|
418
|
+
expect.any(Object),
|
|
419
|
+
]);
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
describe("mockOperationOnce", () => {
|
|
424
|
+
it("should match once", async () => {
|
|
425
|
+
// Arrange
|
|
426
|
+
const mockFetch = mockGqlFetch();
|
|
427
|
+
const operation = {
|
|
428
|
+
type: "query",
|
|
429
|
+
id: "getMyStuff",
|
|
430
|
+
};
|
|
431
|
+
const data = {
|
|
432
|
+
myStuff: "stuff",
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
// Act
|
|
436
|
+
mockFetch.mockOperationOnce({operation}, RespondWith.data(data));
|
|
437
|
+
const result = mockFetch(
|
|
438
|
+
operation,
|
|
439
|
+
{a: "variable"},
|
|
440
|
+
{my: "context"},
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
// Assert
|
|
444
|
+
await expect(result).resolves.toBeDefined();
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
it("should only match once", async () => {
|
|
448
|
+
// Arrange
|
|
449
|
+
const mockFetch = mockGqlFetch();
|
|
450
|
+
const operation = {
|
|
451
|
+
type: "query",
|
|
452
|
+
id: "getMyStuff",
|
|
453
|
+
};
|
|
454
|
+
const data = {
|
|
455
|
+
myStuff: "stuff",
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
// Act
|
|
459
|
+
mockFetch.mockOperationOnce({operation}, RespondWith.data(data));
|
|
460
|
+
const result = Promise.all([
|
|
461
|
+
mockFetch(operation, {a: "variable"}, {my: "context"}),
|
|
462
|
+
mockFetch(operation, {b: "variable"}, {another: "context"}),
|
|
463
|
+
]);
|
|
464
|
+
|
|
465
|
+
// Assert
|
|
466
|
+
await expect(result).rejects.toThrowError();
|
|
467
|
+
});
|
|
468
|
+
});
|
|
469
|
+
});
|