@khanacademy/wonder-blocks-testing-core 1.0.2 → 1.1.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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @khanacademy/wonder-blocks-testing-core
2
2
 
3
+ ## 1.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 16565a85: Add support for hard fails to the request mocking features
8
+
3
9
  ## 1.0.2
4
10
 
5
11
  ### Patch Changes
package/dist/es/index.js CHANGED
@@ -52,19 +52,33 @@ const fetchRequestMatchesMock = (mock, input, init) => {
52
52
 
53
53
  const mockRequester = (operationMatcher, operationToString) => {
54
54
  const mocks = [];
55
- const mockFn = (...args) => {
55
+ const configuration = {
56
+ hardFailOnUnmockedRequests: false
57
+ };
58
+ const getMatchingMock = (...args) => {
56
59
  for (const mock of mocks) {
57
60
  if (mock.onceOnly && mock.used) {
58
61
  continue;
59
62
  }
60
63
  if (operationMatcher(mock.operation, ...args)) {
61
64
  mock.used = true;
62
- return mock.response();
65
+ return mock;
63
66
  }
64
67
  }
68
+ return null;
69
+ };
70
+ const mockFn = (...args) => {
71
+ const matchingMock = getMatchingMock(...args);
72
+ if (matchingMock) {
73
+ return matchingMock.response();
74
+ }
65
75
  const operation = operationToString(...args);
66
- return Promise.reject(new Error(`No matching mock response found for request:
67
- ${operation}`));
76
+ const noMatchError = new Error(`No matching mock response found for request:
77
+ ${operation}`);
78
+ if (configuration.hardFailOnUnmockedRequests) {
79
+ throw noMatchError;
80
+ }
81
+ return Promise.reject(noMatchError);
68
82
  };
69
83
  const addMockedOperation = (operation, response, onceOnly) => {
70
84
  const mockResponse = () => response.toPromise();
@@ -78,6 +92,10 @@ const mockRequester = (operationMatcher, operationToString) => {
78
92
  };
79
93
  mockFn.mockOperation = (operation, response) => addMockedOperation(operation, response, false);
80
94
  mockFn.mockOperationOnce = (operation, response) => addMockedOperation(operation, response, true);
95
+ mockFn.configure = config => {
96
+ Object.assign(configuration, config);
97
+ return mockFn;
98
+ };
81
99
  return mockFn;
82
100
  };
83
101
 
@@ -1,9 +1,58 @@
1
1
  import type { MockResponse } from "../respond-with";
2
+ import { ConfigureFn } from "../types";
2
3
  export type FetchMockOperation = RegExp | string;
3
- type FetchMockOperationFn = (operation: FetchMockOperation, response: MockResponse<any>) => FetchMockFn;
4
+ interface FetchMockOperationFn {
5
+ (
6
+ /**
7
+ * The operation to match.
8
+ *
9
+ * This is a string for an exact match, or a regex. This is compared to
10
+ * to the URL of the fetch request to determine if it is a matching
11
+ * request.
12
+ */
13
+ operation: FetchMockOperation,
14
+ /**
15
+ * The response to return when the operation is matched.
16
+ */
17
+ response: MockResponse<any>): FetchMockFn;
18
+ }
4
19
  export type FetchMockFn = {
20
+ /**
21
+ * The mock fetch function.
22
+ *
23
+ * This function is a drop-in replacement for the fetch function. You should
24
+ * not need to call this function directly. Just replace the normal fetch
25
+ * function implementation with this.
26
+ */
5
27
  (input: RequestInfo, init?: RequestInit): Promise<Response>;
28
+ /**
29
+ * Mock a fetch operation.
30
+ *
31
+ * This adds a response for a given mocked operation. Regardless of how
32
+ * many times this mock is matched, it will be used.
33
+ *
34
+ * @returns The mock fetch function for chaining.
35
+ */
6
36
  mockOperation: FetchMockOperationFn;
37
+ /**
38
+ * Mock a fetch operation once.
39
+ *
40
+ * This adds a response for a given mocked operation that will only be used
41
+ * once and discarded.
42
+ *
43
+ * @returns The mock fetch function for chaining.
44
+ */
7
45
  mockOperationOnce: FetchMockOperationFn;
46
+ /**
47
+ * Configure the mock fetch function with the given configuration.
48
+ *
49
+ * This function is provided as a convenience to allow for configuring the
50
+ * mock fetch function in a fluent manner. The configuration is applied
51
+ * to all mocks for a given fetch function; the last configuration applied
52
+ * will be the one that is used for all mocked operations.
53
+ *
54
+ * @returns The mock fetch function for chaining.
55
+ */
56
+ configure: ConfigureFn<FetchMockOperation, any>;
8
57
  };
9
58
  export {};
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@ export { RespondWith } from "./respond-with";
6
6
  export { SettleController } from "./settle-controller";
7
7
  export type { MockResponse } from "./respond-with";
8
8
  export type { FetchMockFn, FetchMockOperation } from "./fetch/types";
9
- export type { GraphQLJson, MockFn, OperationMock, OperationMatcher, MockOperationFn, } from "./types";
9
+ export type { GraphQLJson, MockFn, OperationMock, OperationMatcher, MockOperationFn, MockConfiguration, ConfigureFn, } from "./types";
10
10
  export * from "./harness/types";
11
11
  export * as harnessAdapters from "./harness/adapters/adapters";
12
12
  export { makeHookHarness } from "./harness/make-hook-harness";
package/dist/index.js CHANGED
@@ -80,19 +80,33 @@ const fetchRequestMatchesMock = (mock, input, init) => {
80
80
 
81
81
  const mockRequester = (operationMatcher, operationToString) => {
82
82
  const mocks = [];
83
- const mockFn = (...args) => {
83
+ const configuration = {
84
+ hardFailOnUnmockedRequests: false
85
+ };
86
+ const getMatchingMock = (...args) => {
84
87
  for (const mock of mocks) {
85
88
  if (mock.onceOnly && mock.used) {
86
89
  continue;
87
90
  }
88
91
  if (operationMatcher(mock.operation, ...args)) {
89
92
  mock.used = true;
90
- return mock.response();
93
+ return mock;
91
94
  }
92
95
  }
96
+ return null;
97
+ };
98
+ const mockFn = (...args) => {
99
+ const matchingMock = getMatchingMock(...args);
100
+ if (matchingMock) {
101
+ return matchingMock.response();
102
+ }
93
103
  const operation = operationToString(...args);
94
- return Promise.reject(new Error(`No matching mock response found for request:
95
- ${operation}`));
104
+ const noMatchError = new Error(`No matching mock response found for request:
105
+ ${operation}`);
106
+ if (configuration.hardFailOnUnmockedRequests) {
107
+ throw noMatchError;
108
+ }
109
+ return Promise.reject(noMatchError);
96
110
  };
97
111
  const addMockedOperation = (operation, response, onceOnly) => {
98
112
  const mockResponse = () => response.toPromise();
@@ -106,6 +120,10 @@ const mockRequester = (operationMatcher, operationToString) => {
106
120
  };
107
121
  mockFn.mockOperation = (operation, response) => addMockedOperation(operation, response, false);
108
122
  mockFn.mockOperationOnce = (operation, response) => addMockedOperation(operation, response, true);
123
+ mockFn.configure = config => {
124
+ Object.assign(configuration, config);
125
+ return mockFn;
126
+ };
109
127
  return mockFn;
110
128
  };
111
129
 
@@ -2,4 +2,4 @@ import type { OperationMatcher, MockFn } from "./types";
2
2
  /**
3
3
  * A generic mock request function for using when mocking fetch or gqlFetch.
4
4
  */
5
- export declare const mockRequester: <TOperationType>(operationMatcher: OperationMatcher<any>, operationToString: (...args: Array<any>) => string) => MockFn<TOperationType>;
5
+ export declare const mockRequester: <TOperationType, TResponseData>(operationMatcher: OperationMatcher<any>, operationToString: (...args: Array<any>) => string) => MockFn<TOperationType, TResponseData>;
package/dist/types.d.ts CHANGED
@@ -10,11 +10,43 @@ export type GraphQLJson<TData extends Record<any, any>> = {
10
10
  message: string;
11
11
  }>;
12
12
  };
13
- export type MockFn<TOperationType> = {
13
+ export interface MockFn<TOperationType, TResponseData> {
14
+ /**
15
+ * The mock fetch function.
16
+ *
17
+ * This function is a drop-in replacement for the fetch function being
18
+ * mocked. It is recommended that a more strongly-typed definition is
19
+ * provided in the consuming codebase, as this definition is intentionally
20
+ * loose to allow for mocking any fetch operation.
21
+ */
14
22
  (...args: Array<any>): Promise<Response>;
15
- mockOperation: MockOperationFn<TOperationType>;
16
- mockOperationOnce: MockOperationFn<TOperationType>;
17
- };
23
+ /**
24
+ * Mock a fetch operation.
25
+ *
26
+ * This adds a response for a given mocked operation of the given type.
27
+ * Matches are determined by the operation matcher provided to the
28
+ * mockRequester function that creates the mock fetch function.
29
+ */
30
+ mockOperation: MockOperationFn<TOperationType, TResponseData>;
31
+ /**
32
+ * Mock a fetch operation once.
33
+ *
34
+ * This adds a response for a given mocked operation of the given type that
35
+ * will only be used once and discarded. Matches are determined by the
36
+ * operation matcher provided to the mockRequester function that creates the
37
+ * mock fetch function.
38
+ */
39
+ mockOperationOnce: MockOperationFn<TOperationType, TResponseData>;
40
+ /**
41
+ * Configure the mock fetch function with the given configuration.
42
+ *
43
+ * This function is provided as a convenience to allow for configuring the
44
+ * mock fetch function in a fluent manner. The configuration is applied
45
+ * to all mocks for a given fetch function; the last configuration applied
46
+ * will be the one that is used for all mocked operations.
47
+ */
48
+ configure: ConfigureFn<TOperationType, TResponseData>;
49
+ }
18
50
  export type OperationMock<TOperation> = {
19
51
  operation: TOperation;
20
52
  onceOnly: boolean;
@@ -22,4 +54,34 @@ export type OperationMock<TOperation> = {
22
54
  response: () => Promise<Response>;
23
55
  };
24
56
  export type OperationMatcher<TOperation> = (operation: TOperation, ...args: Array<any>) => boolean;
25
- export type MockOperationFn<TOperationType> = <TOperation extends TOperationType>(operation: TOperation, response: MockResponse<any>) => MockFn<TOperationType>;
57
+ export type MockOperationFn<TOperationType, TResponseData> = <TOperation extends TOperationType>(operation: TOperation, response: MockResponse<TResponseData>) => MockFn<TOperationType, TResponseData>;
58
+ /**
59
+ * Configuration options for mocked fetches.
60
+ */
61
+ export type MockConfiguration = {
62
+ /**
63
+ * If true, any requests that don't match a mock will throw an error
64
+ * immediately on the request being made; otherwise, if false, unmatched
65
+ * requests will return a rejected promise.
66
+ *
67
+ * Defaults to false. When true, this is akin to the Apollo MockLink
68
+ * behavior that throws upon the request being. This is useful as it will
69
+ * clearly fail a test early, indicating that a request was not mocked.
70
+ * However, that mode requires all requests to be mocked, which can be
71
+ * cumbersome and unncessary. Having unmocked requests return a rejected
72
+ * promise is more flexible and allows for more granular control over
73
+ * mocking, allowing developers to mock only the requests they care about
74
+ * and let the error handling of their code deal with the rejected promises.
75
+ */
76
+ hardFailOnUnmockedRequests: boolean;
77
+ };
78
+ export interface ConfigureFn<TOperationType, TResponseData> {
79
+ /**
80
+ * Configure the mock fetch function with the given configuration.
81
+ *
82
+ * @param config The configuration changes to apply to the mock fetch
83
+ * function.
84
+ * @returns The mock fetch function .
85
+ */
86
+ (config: Partial<MockConfiguration>): MockFn<TOperationType, TResponseData>;
87
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khanacademy/wonder-blocks-testing-core",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "design": "v1",
5
5
  "publishConfig": {
6
6
  "access": "public"