@khanacademy/wonder-blocks-testing 3.0.1 → 4.0.2
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 +27 -0
- package/dist/es/index.js +144 -323
- package/dist/index.js +259 -138
- package/package.json +4 -3
- package/src/{gql/__tests__/make-gql-mock-response.test.js → __tests__/make-mock-response.test.js} +196 -34
- package/src/__tests__/mock-requester.test.js +213 -0
- package/src/__tests__/response-impl.test.js +47 -0
- package/src/fetch/__tests__/__snapshots__/mock-fetch.test.js.snap +29 -0
- package/src/fetch/__tests__/fetch-request-matches-mock.test.js +99 -0
- package/src/fetch/__tests__/mock-fetch.test.js +84 -0
- package/src/fetch/fetch-request-matches-mock.js +43 -0
- package/src/fetch/mock-fetch.js +19 -0
- package/src/fetch/types.js +18 -0
- package/src/gql/__tests__/mock-gql-fetch.test.js +24 -15
- package/src/gql/__tests__/wb-data-integration.test.js +7 -4
- package/src/gql/mock-gql-fetch.js +9 -80
- package/src/gql/types.js +11 -10
- package/src/index.js +9 -3
- package/src/make-mock-response.js +150 -0
- package/src/mock-requester.js +75 -0
- package/src/response-impl.js +9 -0
- package/src/types.js +39 -0
- package/src/gql/make-gql-mock-response.js +0 -124
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import {Request} from "node-fetch";
|
|
3
|
+
import {fetchRequestMatchesMock} from "../fetch-request-matches-mock.js";
|
|
4
|
+
|
|
5
|
+
const TEST_URL = "http://example.com/foo?querya=1&queryb=elephants#fragment";
|
|
6
|
+
|
|
7
|
+
describe("#fetchRequestMatchesMock", () => {
|
|
8
|
+
it("should throw if mock is not valid", () => {
|
|
9
|
+
// Arrange
|
|
10
|
+
const mock = {
|
|
11
|
+
operation: {
|
|
12
|
+
id: "foo",
|
|
13
|
+
type: "query",
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Act
|
|
18
|
+
const underTest = () =>
|
|
19
|
+
fetchRequestMatchesMock((mock: any), TEST_URL, null);
|
|
20
|
+
|
|
21
|
+
// Assert
|
|
22
|
+
expect(underTest).toThrowErrorMatchingInlineSnapshot(
|
|
23
|
+
`"Unsupported mock operation: {\\"operation\\":{\\"id\\":\\"foo\\",\\"type\\":\\"query\\"}}"`,
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("should throw if input is not valid", () => {
|
|
28
|
+
// Arrange
|
|
29
|
+
const mock = "http://example.com/foo";
|
|
30
|
+
|
|
31
|
+
// Act
|
|
32
|
+
const underTest = () =>
|
|
33
|
+
fetchRequestMatchesMock(
|
|
34
|
+
mock,
|
|
35
|
+
({not: "a valid request"}: any),
|
|
36
|
+
null,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
// Assert
|
|
40
|
+
expect(underTest).toThrowErrorMatchingInlineSnapshot(
|
|
41
|
+
`"Unsupported input type"`,
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe.each([TEST_URL, new URL(TEST_URL), new Request(TEST_URL)])(
|
|
46
|
+
"for valid inputs",
|
|
47
|
+
(input) => {
|
|
48
|
+
it("should return false if mock is a string and it does not match the fetched URL", () => {
|
|
49
|
+
// Arrange
|
|
50
|
+
const mock = "http://example.com/bar";
|
|
51
|
+
|
|
52
|
+
// Act
|
|
53
|
+
const result = fetchRequestMatchesMock(mock, input, null);
|
|
54
|
+
|
|
55
|
+
// Assert
|
|
56
|
+
expect(result).toBe(false);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("should return false if the mock is a regular expression and it doesn't match the fetched URL", () => {
|
|
60
|
+
// Arrange
|
|
61
|
+
const mock = /\/bar/;
|
|
62
|
+
|
|
63
|
+
// Act
|
|
64
|
+
const result = fetchRequestMatchesMock(mock, input, null);
|
|
65
|
+
|
|
66
|
+
// Assert
|
|
67
|
+
expect(result).toBe(false);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("should return true if the mock is a string and matches the fetched URL", () => {
|
|
71
|
+
// Arrange
|
|
72
|
+
const mock = TEST_URL;
|
|
73
|
+
|
|
74
|
+
// Act
|
|
75
|
+
const result = fetchRequestMatchesMock(mock, input, null);
|
|
76
|
+
|
|
77
|
+
// Assert
|
|
78
|
+
expect(result).toBe(true);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it.each([
|
|
82
|
+
/http:\/\/example.com\/foo/,
|
|
83
|
+
/queryb=elephants/,
|
|
84
|
+
/^.*#fragment$/,
|
|
85
|
+
])(
|
|
86
|
+
"should return true if the mock is a %s and matches the fetched URL",
|
|
87
|
+
(regex) => {
|
|
88
|
+
// Arrange
|
|
89
|
+
|
|
90
|
+
// Act
|
|
91
|
+
const result = fetchRequestMatchesMock(regex, input, null);
|
|
92
|
+
|
|
93
|
+
// Assert
|
|
94
|
+
expect(result).toBe(true);
|
|
95
|
+
},
|
|
96
|
+
);
|
|
97
|
+
},
|
|
98
|
+
);
|
|
99
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import {Request} from "node-fetch";
|
|
3
|
+
import {RespondWith} from "../../make-mock-response.js";
|
|
4
|
+
import {mockFetch} from "../mock-fetch.js";
|
|
5
|
+
|
|
6
|
+
describe("#mockFetch", () => {
|
|
7
|
+
it.each`
|
|
8
|
+
input | init
|
|
9
|
+
${"http://example.com/foo"} | ${undefined}
|
|
10
|
+
${new URL("http://example.com/foo")} | ${{method: "GET"}}
|
|
11
|
+
${new Request("http://example.com/foo")} | ${{method: "POST"}}
|
|
12
|
+
`(
|
|
13
|
+
"should reject with a useful error when there are no matching mocks for %s",
|
|
14
|
+
async ({input, init}) => {
|
|
15
|
+
// Arrange
|
|
16
|
+
const mockFn = mockFetch();
|
|
17
|
+
|
|
18
|
+
// Act
|
|
19
|
+
const result = mockFn(input, init);
|
|
20
|
+
|
|
21
|
+
// Assert
|
|
22
|
+
await expect(result).rejects.toThrowErrorMatchingSnapshot();
|
|
23
|
+
},
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
describe("mockOperation", () => {
|
|
27
|
+
it("should match a similar operation", async () => {
|
|
28
|
+
// Arrange
|
|
29
|
+
const mockFn = mockFetch();
|
|
30
|
+
const operation = "http://example.com/foo";
|
|
31
|
+
|
|
32
|
+
// Act
|
|
33
|
+
mockFn.mockOperation(operation, RespondWith.text("TADA!"));
|
|
34
|
+
const result = mockFn(operation, {method: "GET"});
|
|
35
|
+
|
|
36
|
+
// Assert
|
|
37
|
+
await expect(result).resolves.toBeDefined();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should not match a different operation", async () => {
|
|
41
|
+
// Arrange
|
|
42
|
+
const mockFn = mockFetch();
|
|
43
|
+
const operation = "http://example.com/foo";
|
|
44
|
+
|
|
45
|
+
// Act
|
|
46
|
+
mockFn.mockOperation(operation, RespondWith.text("TADA!"));
|
|
47
|
+
const result = mockFn("http://example.com/bar", {method: "GET"});
|
|
48
|
+
|
|
49
|
+
// Assert
|
|
50
|
+
await expect(result).rejects.toThrowError();
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("mockOperationOnce", () => {
|
|
55
|
+
it("should match once", async () => {
|
|
56
|
+
// Arrange
|
|
57
|
+
const mockFn = mockFetch();
|
|
58
|
+
const operation = "http://example.com/foo";
|
|
59
|
+
|
|
60
|
+
// Act
|
|
61
|
+
mockFn.mockOperationOnce(operation, RespondWith.text("TADA!"));
|
|
62
|
+
const result = mockFn(operation, {method: "GET"});
|
|
63
|
+
|
|
64
|
+
// Assert
|
|
65
|
+
await expect(result).resolves.toBeDefined();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("should only match once", async () => {
|
|
69
|
+
// Arrange
|
|
70
|
+
const mockFn = mockFetch();
|
|
71
|
+
const operation = "http://example.com/foo";
|
|
72
|
+
|
|
73
|
+
// Act
|
|
74
|
+
mockFn.mockOperationOnce(operation, RespondWith.text("TADA!"));
|
|
75
|
+
const result = Promise.all([
|
|
76
|
+
mockFn(operation, {method: "GET"}),
|
|
77
|
+
mockFn(operation, {method: "POST"}),
|
|
78
|
+
]);
|
|
79
|
+
|
|
80
|
+
// Assert
|
|
81
|
+
await expect(result).rejects.toThrowError();
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import type {FetchMockOperation} from "./types.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Get the URL from the given RequestInfo.
|
|
6
|
+
*
|
|
7
|
+
* Since we could be running in Node or in JSDOM, we don't check instance
|
|
8
|
+
* types, but just use a heuristic so that this works without knowing what
|
|
9
|
+
* was polyfilling things.
|
|
10
|
+
*/
|
|
11
|
+
const getHref = (input: RequestInfo): string => {
|
|
12
|
+
if (typeof input === "string") {
|
|
13
|
+
return input;
|
|
14
|
+
} else if (typeof input.url === "string") {
|
|
15
|
+
return input.url;
|
|
16
|
+
} else if (typeof input.href === "string") {
|
|
17
|
+
return input.href;
|
|
18
|
+
} else {
|
|
19
|
+
throw new Error(`Unsupported input type`);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Determines if a given fetch invocation matches the given mock.
|
|
25
|
+
*/
|
|
26
|
+
export const fetchRequestMatchesMock = (
|
|
27
|
+
mock: FetchMockOperation,
|
|
28
|
+
input: RequestInfo,
|
|
29
|
+
init: ?RequestOptions,
|
|
30
|
+
): boolean => {
|
|
31
|
+
// Currently, we only match on the input portion.
|
|
32
|
+
// This can be a Request, a URL, or a string.
|
|
33
|
+
const href = getHref(input);
|
|
34
|
+
|
|
35
|
+
// Our mock operation is either a string for an exact match, or a regex.
|
|
36
|
+
if (typeof mock === "string") {
|
|
37
|
+
return href === mock;
|
|
38
|
+
} else if (mock instanceof RegExp) {
|
|
39
|
+
return mock.test(href);
|
|
40
|
+
} else {
|
|
41
|
+
throw new Error(`Unsupported mock operation: ${JSON.stringify(mock)}`);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import {fetchRequestMatchesMock} from "./fetch-request-matches-mock.js";
|
|
3
|
+
import {mockRequester} from "../mock-requester.js";
|
|
4
|
+
import type {FetchMockFn, FetchMockOperation} from "./types.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A mock for the fetch function passed to GqlRouter.
|
|
8
|
+
*/
|
|
9
|
+
export const mockFetch = (): FetchMockFn =>
|
|
10
|
+
mockRequester<FetchMockOperation, _>(
|
|
11
|
+
fetchRequestMatchesMock,
|
|
12
|
+
(input, init) =>
|
|
13
|
+
`Input: ${
|
|
14
|
+
typeof input === "string"
|
|
15
|
+
? input
|
|
16
|
+
: JSON.stringify(input, null, 2)
|
|
17
|
+
}
|
|
18
|
+
Options: ${init == null ? "None" : JSON.stringify(init, null, 2)}`,
|
|
19
|
+
);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//@flow
|
|
2
|
+
import type {OperationMock} from "../types.js";
|
|
3
|
+
import type {MockResponse} from "../make-mock-response.js";
|
|
4
|
+
|
|
5
|
+
export type FetchMockOperation = RegExp | string;
|
|
6
|
+
|
|
7
|
+
type FetchMockOperationFn = (
|
|
8
|
+
operation: FetchMockOperation,
|
|
9
|
+
response: MockResponse<any>,
|
|
10
|
+
) => FetchMockFn;
|
|
11
|
+
|
|
12
|
+
export type FetchMockFn = {|
|
|
13
|
+
(input: RequestInfo, init?: RequestOptions): Promise<Response>,
|
|
14
|
+
mockOperation: FetchMockOperationFn,
|
|
15
|
+
mockOperationOnce: FetchMockOperationFn,
|
|
16
|
+
|};
|
|
17
|
+
|
|
18
|
+
export type FetchMock = OperationMock<FetchMockOperation>;
|
|
@@ -3,7 +3,7 @@ import * as React from "react";
|
|
|
3
3
|
import {render, screen, waitFor} from "@testing-library/react";
|
|
4
4
|
|
|
5
5
|
import {GqlRouter, useGql} from "@khanacademy/wonder-blocks-data";
|
|
6
|
-
import {RespondWith} from "
|
|
6
|
+
import {RespondWith} from "../../make-mock-response.js";
|
|
7
7
|
import {mockGqlFetch} from "../mock-gql-fetch.js";
|
|
8
8
|
|
|
9
9
|
describe("#mockGqlFetch", () => {
|
|
@@ -37,7 +37,7 @@ describe("#mockGqlFetch", () => {
|
|
|
37
37
|
// Assert
|
|
38
38
|
await waitFor(() =>
|
|
39
39
|
expect(result).toHaveTextContent(
|
|
40
|
-
"No matching
|
|
40
|
+
"No matching mock response found for request",
|
|
41
41
|
),
|
|
42
42
|
);
|
|
43
43
|
});
|
|
@@ -65,7 +65,10 @@ describe("#mockGqlFetch", () => {
|
|
|
65
65
|
};
|
|
66
66
|
|
|
67
67
|
// Act
|
|
68
|
-
mockFetch.mockOperation(
|
|
68
|
+
mockFetch.mockOperation(
|
|
69
|
+
{operation: query},
|
|
70
|
+
RespondWith.graphQLData(data),
|
|
71
|
+
);
|
|
69
72
|
render(
|
|
70
73
|
<GqlRouter defaultContext={{}} fetch={mockFetch}>
|
|
71
74
|
<RenderData />
|
|
@@ -171,7 +174,7 @@ describe("#mockGqlFetch", () => {
|
|
|
171
174
|
|
|
172
175
|
// Assert
|
|
173
176
|
await expect(result).rejects.toThrowErrorMatchingInlineSnapshot(`
|
|
174
|
-
"No matching
|
|
177
|
+
"No matching mock response found for request:
|
|
175
178
|
Operation: query getMyStuff
|
|
176
179
|
Variables: {
|
|
177
180
|
\\"a\\": \\"variable\\"
|
|
@@ -195,7 +198,7 @@ describe("#mockGqlFetch", () => {
|
|
|
195
198
|
};
|
|
196
199
|
|
|
197
200
|
// Act
|
|
198
|
-
mockFetch.mockOperation({operation}, RespondWith.
|
|
201
|
+
mockFetch.mockOperation({operation}, RespondWith.graphQLData(data));
|
|
199
202
|
const result = mockFetch(
|
|
200
203
|
operation,
|
|
201
204
|
{a: "variable"},
|
|
@@ -218,7 +221,7 @@ describe("#mockGqlFetch", () => {
|
|
|
218
221
|
};
|
|
219
222
|
|
|
220
223
|
// Act
|
|
221
|
-
mockFetch.mockOperation({operation}, RespondWith.
|
|
224
|
+
mockFetch.mockOperation({operation}, RespondWith.graphQLData(data));
|
|
222
225
|
const result = mockFetch(
|
|
223
226
|
{type: "mutation", id: "putMyStuff"},
|
|
224
227
|
{a: "variable"},
|
|
@@ -246,7 +249,7 @@ describe("#mockGqlFetch", () => {
|
|
|
246
249
|
// Act
|
|
247
250
|
mockFetch.mockOperation(
|
|
248
251
|
{operation, variables},
|
|
249
|
-
RespondWith.
|
|
252
|
+
RespondWith.graphQLData(data),
|
|
250
253
|
);
|
|
251
254
|
const result = mockFetch(operation, variables, {my: "context"});
|
|
252
255
|
|
|
@@ -271,7 +274,7 @@ describe("#mockGqlFetch", () => {
|
|
|
271
274
|
// Act
|
|
272
275
|
mockFetch.mockOperation(
|
|
273
276
|
{operation, variables},
|
|
274
|
-
RespondWith.
|
|
277
|
+
RespondWith.graphQLData(data),
|
|
275
278
|
);
|
|
276
279
|
const result = mockFetch(
|
|
277
280
|
operation,
|
|
@@ -300,7 +303,7 @@ describe("#mockGqlFetch", () => {
|
|
|
300
303
|
// Act
|
|
301
304
|
mockFetch.mockOperation(
|
|
302
305
|
{operation, context},
|
|
303
|
-
RespondWith.
|
|
306
|
+
RespondWith.graphQLData(data),
|
|
304
307
|
);
|
|
305
308
|
const result = mockFetch(operation, {a: "variable"}, context);
|
|
306
309
|
|
|
@@ -325,7 +328,7 @@ describe("#mockGqlFetch", () => {
|
|
|
325
328
|
// Act
|
|
326
329
|
mockFetch.mockOperation(
|
|
327
330
|
{operation, context},
|
|
328
|
-
RespondWith.
|
|
331
|
+
RespondWith.graphQLData(data),
|
|
329
332
|
);
|
|
330
333
|
const result = mockFetch(
|
|
331
334
|
operation,
|
|
@@ -357,7 +360,7 @@ describe("#mockGqlFetch", () => {
|
|
|
357
360
|
// Act
|
|
358
361
|
mockFetch.mockOperation(
|
|
359
362
|
{operation, variables, context},
|
|
360
|
-
RespondWith.
|
|
363
|
+
RespondWith.graphQLData(data),
|
|
361
364
|
);
|
|
362
365
|
const result = mockFetch(operation, variables, context);
|
|
363
366
|
|
|
@@ -385,7 +388,7 @@ describe("#mockGqlFetch", () => {
|
|
|
385
388
|
// Act
|
|
386
389
|
mockFetch.mockOperation(
|
|
387
390
|
{operation, variables, context},
|
|
388
|
-
RespondWith.
|
|
391
|
+
RespondWith.graphQLData(data),
|
|
389
392
|
);
|
|
390
393
|
const response = await mockFetch(operation, variables, context);
|
|
391
394
|
const result = await response.text();
|
|
@@ -406,7 +409,7 @@ describe("#mockGqlFetch", () => {
|
|
|
406
409
|
};
|
|
407
410
|
|
|
408
411
|
// Act
|
|
409
|
-
mockFetch.mockOperation({operation}, RespondWith.
|
|
412
|
+
mockFetch.mockOperation({operation}, RespondWith.graphQLData(data));
|
|
410
413
|
const result = Promise.all([
|
|
411
414
|
mockFetch(operation, {a: "variable"}, {my: "context"}),
|
|
412
415
|
mockFetch(operation, {b: "variable"}, {another: "context"}),
|
|
@@ -433,7 +436,10 @@ describe("#mockGqlFetch", () => {
|
|
|
433
436
|
};
|
|
434
437
|
|
|
435
438
|
// Act
|
|
436
|
-
mockFetch.mockOperationOnce(
|
|
439
|
+
mockFetch.mockOperationOnce(
|
|
440
|
+
{operation},
|
|
441
|
+
RespondWith.graphQLData(data),
|
|
442
|
+
);
|
|
437
443
|
const result = mockFetch(
|
|
438
444
|
operation,
|
|
439
445
|
{a: "variable"},
|
|
@@ -456,7 +462,10 @@ describe("#mockGqlFetch", () => {
|
|
|
456
462
|
};
|
|
457
463
|
|
|
458
464
|
// Act
|
|
459
|
-
mockFetch.mockOperationOnce(
|
|
465
|
+
mockFetch.mockOperationOnce(
|
|
466
|
+
{operation},
|
|
467
|
+
RespondWith.graphQLData(data),
|
|
468
|
+
);
|
|
460
469
|
const result = Promise.all([
|
|
461
470
|
mockFetch(operation, {a: "variable"}, {my: "context"}),
|
|
462
471
|
mockFetch(operation, {b: "variable"}, {another: "context"}),
|
|
@@ -3,7 +3,7 @@ import * as React from "react";
|
|
|
3
3
|
import {render, screen, waitFor} from "@testing-library/react";
|
|
4
4
|
|
|
5
5
|
import {GqlRouter, useGql} from "@khanacademy/wonder-blocks-data";
|
|
6
|
-
import {RespondWith} from "
|
|
6
|
+
import {RespondWith} from "../../make-mock-response.js";
|
|
7
7
|
import {mockGqlFetch} from "../mock-gql-fetch.js";
|
|
8
8
|
|
|
9
9
|
describe("integrating mockGqlFetch, RespondWith, GqlRouter and useGql", () => {
|
|
@@ -36,12 +36,12 @@ describe("integrating mockGqlFetch, RespondWith, GqlRouter and useGql", () => {
|
|
|
36
36
|
// Assert
|
|
37
37
|
await waitFor(() =>
|
|
38
38
|
expect(result).toHaveTextContent(
|
|
39
|
-
"No matching
|
|
39
|
+
"No matching mock response found for request",
|
|
40
40
|
),
|
|
41
41
|
);
|
|
42
42
|
});
|
|
43
43
|
|
|
44
|
-
it("should resolve with data for RespondWith.
|
|
44
|
+
it("should resolve with data for RespondWith.graphQLData", async () => {
|
|
45
45
|
// Arrange
|
|
46
46
|
const mockFetch = mockGqlFetch();
|
|
47
47
|
const query = {
|
|
@@ -64,7 +64,10 @@ describe("integrating mockGqlFetch, RespondWith, GqlRouter and useGql", () => {
|
|
|
64
64
|
};
|
|
65
65
|
|
|
66
66
|
// Act
|
|
67
|
-
mockFetch.mockOperation(
|
|
67
|
+
mockFetch.mockOperation(
|
|
68
|
+
{operation: query},
|
|
69
|
+
RespondWith.graphQLData(data),
|
|
70
|
+
);
|
|
68
71
|
render(
|
|
69
72
|
<GqlRouter defaultContext={{}} fetch={mockFetch}>
|
|
70
73
|
<RenderData />
|
|
@@ -1,89 +1,18 @@
|
|
|
1
1
|
// @flow
|
|
2
|
-
import type {GqlContext} from "@khanacademy/wonder-blocks-data";
|
|
3
2
|
import {gqlRequestMatchesMock} from "./gql-request-matches-mock.js";
|
|
4
|
-
import {
|
|
5
|
-
import type {
|
|
6
|
-
import type {GqlMock, GqlMockOperation, GqlFetchMockFn} from "./types.js";
|
|
3
|
+
import {mockRequester} from "../mock-requester.js";
|
|
4
|
+
import type {GqlFetchMockFn, GqlMockOperation} from "./types.js";
|
|
7
5
|
|
|
8
6
|
/**
|
|
9
7
|
* A mock for the fetch function passed to GqlRouter.
|
|
10
8
|
*/
|
|
11
|
-
export const mockGqlFetch = (): GqlFetchMockFn =>
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
// What we return has to be a drop in for the fetch function that is
|
|
18
|
-
// provided to `GqlRouter` which is how folks will then use this mock.
|
|
19
|
-
const gqlFetchMock: GqlFetchMockFn = (
|
|
20
|
-
operation,
|
|
21
|
-
variables,
|
|
22
|
-
context,
|
|
23
|
-
): Promise<Response> => {
|
|
24
|
-
// Iterate our mocked operations and find the first one that matches.
|
|
25
|
-
for (const mock of mocks) {
|
|
26
|
-
if (mock.onceOnly && mock.used) {
|
|
27
|
-
// This is a once-only mock and it has been used, so skip it.
|
|
28
|
-
continue;
|
|
29
|
-
}
|
|
30
|
-
if (
|
|
31
|
-
gqlRequestMatchesMock(
|
|
32
|
-
mock.operation,
|
|
33
|
-
operation,
|
|
34
|
-
variables,
|
|
35
|
-
context,
|
|
36
|
-
)
|
|
37
|
-
) {
|
|
38
|
-
mock.used = true;
|
|
39
|
-
return mock.response();
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Default is to reject with some helpful info on what request
|
|
44
|
-
// we rejected.
|
|
45
|
-
return Promise.reject(
|
|
46
|
-
new Error(`No matching GraphQL mock response found for request:
|
|
47
|
-
Operation: ${operation.type} ${operation.id}
|
|
9
|
+
export const mockGqlFetch = (): GqlFetchMockFn =>
|
|
10
|
+
mockRequester<GqlMockOperation<any, any, any>, _>(
|
|
11
|
+
gqlRequestMatchesMock,
|
|
12
|
+
(operation, variables, context) =>
|
|
13
|
+
`Operation: ${operation.type} ${operation.id}
|
|
48
14
|
Variables: ${
|
|
49
15
|
variables == null ? "None" : JSON.stringify(variables, null, 2)
|
|
50
16
|
}
|
|
51
|
-
Context: ${JSON.stringify(context, null, 2)}
|
|
52
|
-
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
const addMockedOperation = <TData, TVariables: {...}, TContext: GqlContext>(
|
|
56
|
-
operation: GqlMockOperation<TData, TVariables, TContext>,
|
|
57
|
-
response: GqlMockResponse<TData>,
|
|
58
|
-
onceOnly: boolean,
|
|
59
|
-
): GqlFetchMockFn => {
|
|
60
|
-
const mockResponse = () => makeGqlMockResponse(response);
|
|
61
|
-
mocks.push({
|
|
62
|
-
operation,
|
|
63
|
-
response: mockResponse,
|
|
64
|
-
onceOnly,
|
|
65
|
-
used: false,
|
|
66
|
-
});
|
|
67
|
-
return gqlFetchMock;
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
gqlFetchMock.mockOperation = <
|
|
71
|
-
TData,
|
|
72
|
-
TVariables: {...},
|
|
73
|
-
TContext: GqlContext,
|
|
74
|
-
>(
|
|
75
|
-
operation: GqlMockOperation<TData, TVariables, TContext>,
|
|
76
|
-
response: GqlMockResponse<TData>,
|
|
77
|
-
): GqlFetchMockFn => addMockedOperation(operation, response, false);
|
|
78
|
-
|
|
79
|
-
gqlFetchMock.mockOperationOnce = <
|
|
80
|
-
TData,
|
|
81
|
-
TVariables: {...},
|
|
82
|
-
TContext: GqlContext,
|
|
83
|
-
>(
|
|
84
|
-
operation: GqlMockOperation<TData, TVariables, TContext>,
|
|
85
|
-
response: GqlMockResponse<TData>,
|
|
86
|
-
): GqlFetchMockFn => addMockedOperation(operation, response, true);
|
|
87
|
-
|
|
88
|
-
return gqlFetchMock;
|
|
89
|
-
};
|
|
17
|
+
Context: ${JSON.stringify(context, null, 2)}`,
|
|
18
|
+
);
|
package/src/gql/types.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
//@flow
|
|
2
2
|
import type {GqlOperation, GqlContext} from "@khanacademy/wonder-blocks-data";
|
|
3
|
-
import type {
|
|
3
|
+
import type {OperationMock, GraphQLJson} from "../types.js";
|
|
4
|
+
import type {MockResponse} from "../make-mock-response.js";
|
|
4
5
|
|
|
5
6
|
export type GqlMockOperation<
|
|
6
|
-
TData,
|
|
7
|
+
TData: {...},
|
|
7
8
|
TVariables: {...},
|
|
8
9
|
TContext: GqlContext,
|
|
9
10
|
> = {|
|
|
@@ -12,9 +13,14 @@ export type GqlMockOperation<
|
|
|
12
13
|
context?: TContext,
|
|
13
14
|
|};
|
|
14
15
|
|
|
15
|
-
type GqlMockOperationFn = <
|
|
16
|
+
type GqlMockOperationFn = <
|
|
17
|
+
TData: {...},
|
|
18
|
+
TVariables: {...},
|
|
19
|
+
TContext: GqlContext,
|
|
20
|
+
TResponseData: GraphQLJson<TData>,
|
|
21
|
+
>(
|
|
16
22
|
operation: GqlMockOperation<TData, TVariables, TContext>,
|
|
17
|
-
response:
|
|
23
|
+
response: MockResponse<TResponseData>,
|
|
18
24
|
) => GqlFetchMockFn;
|
|
19
25
|
|
|
20
26
|
export type GqlFetchMockFn = {|
|
|
@@ -27,9 +33,4 @@ export type GqlFetchMockFn = {|
|
|
|
27
33
|
mockOperationOnce: GqlMockOperationFn,
|
|
28
34
|
|};
|
|
29
35
|
|
|
30
|
-
export type GqlMock =
|
|
31
|
-
operation: GqlMockOperation<any, any, any>,
|
|
32
|
-
onceOnly: boolean,
|
|
33
|
-
used: boolean,
|
|
34
|
-
response: () => Promise<Response>,
|
|
35
|
-
|};
|
|
36
|
+
export type GqlMock = OperationMock<GqlMockOperation<any, any, any>>;
|
package/src/index.js
CHANGED
|
@@ -17,8 +17,14 @@ export type {
|
|
|
17
17
|
FixturesOptions,
|
|
18
18
|
} from "./fixtures/types.js";
|
|
19
19
|
|
|
20
|
-
//
|
|
20
|
+
// Fetch mocking framework
|
|
21
|
+
export type {MockResponse} from "./make-mock-response.js";
|
|
22
|
+
export {RespondWith} from "./make-mock-response.js";
|
|
23
|
+
export {mockFetch} from "./fetch/mock-fetch.js";
|
|
24
|
+
export type {
|
|
25
|
+
FetchMockFn,
|
|
26
|
+
FetchMock,
|
|
27
|
+
FetchMockOperation,
|
|
28
|
+
} from "./fetch/types.js";
|
|
21
29
|
export {mockGqlFetch} from "./gql/mock-gql-fetch.js";
|
|
22
|
-
export type {GqlMockResponse} from "./gql/make-gql-mock-response.js";
|
|
23
|
-
export {RespondWith} from "./gql/make-gql-mock-response.js";
|
|
24
30
|
export type {GqlFetchMockFn, GqlMock, GqlMockOperation} from "./gql/types.js";
|