Skip to content

Commit 3527b41

Browse files
authored
Configure behaviour for missing request handlers (#55)
1 parent ddbd958 commit 3527b41

File tree

5 files changed

+102
-18
lines changed

5 files changed

+102
-18
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
node_modules
22
dist
3-
.DS_Store
3+
.idea
4+
.DS_Store

README.md

+15
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,21 @@ const cache = new InMemoryCache({
276276
const mockClient = createMockClient({ cache });
277277
```
278278

279+
Additionally, you can specify a `missingHandlerPolicy` to define the behavior of the mock client when a request handler for a particular operation is not found.
280+
281+
The `missingHandlerPolicy` accepts one of three string values:
282+
- `'throw-error'`: The client throws an error when it encounters a missing handler.
283+
- `'warn-and-return-error'`: The client logs a warning message in the console and returns an error.
284+
- `'return-error'`: The client returns an error without any warning message.
285+
286+
Here's an example of how you can set the `missingHandlerPolicy`:
287+
288+
```typescript
289+
const mockClient = createMockClient({ missingHandlerPolicy: 'warn-and-return-error' });
290+
```
291+
292+
In this example, if a request handler for a given operation is not found, the client will log a warning message to the console and then return an error.
293+
279294
Note: it is not possible to specify the `link` to use as this is how `mock-apollo-client` injects its behaviour.
280295

281296
### Fragments

src/mockClient.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ApolloClientOptions, ApolloClient, DocumentNode } from '@apollo/client/core';
22
import { InMemoryCache as Cache, NormalizedCacheObject } from '@apollo/client/cache';
3-
import { MockLink } from './mockLink';
3+
import { MissingHandlerPolicy, MockLink } from './mockLink';
44
import { IMockSubscription } from './mockSubscription';
55

66
export type RequestHandler<TData = any, TVariables = any> =
@@ -15,7 +15,11 @@ export type RequestHandlerResponse<T> =
1515
export type MockApolloClient = ApolloClient<NormalizedCacheObject> &
1616
{ setRequestHandler: (query: DocumentNode, handler: RequestHandler) => void };
1717

18-
export type MockApolloClientOptions = Partial<Omit<ApolloClientOptions<NormalizedCacheObject>, 'link'>> | undefined;
18+
interface CustomOptions {
19+
missingHandlerPolicy?: MissingHandlerPolicy;
20+
}
21+
22+
export type MockApolloClientOptions = Partial<Omit<ApolloClientOptions<NormalizedCacheObject>, 'link'>> & CustomOptions | undefined;
1923

2024
export const createMockClient = (options?: MockApolloClientOptions): MockApolloClient => {
2125
if ((options as any)?.link) {

src/mockLink.test.ts

+50-13
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ describe('class MockLink', () => {
1010
const queryOne = gql`query One {one}`;
1111
const queryTwo = gql`query Two {two}`;
1212

13+
const queryOneOperation = { query: queryOne, variables: { a: 'one' } } as Partial<Operation> as Operation;
14+
15+
const createMockObserver = (): jest.Mocked<Observer<any>> => ({
16+
next: jest.fn(),
17+
error: jest.fn(),
18+
complete: jest.fn(),
19+
});
20+
1321
beforeEach(() => {
1422
jest.spyOn(console, 'warn')
1523
.mockReset();
@@ -81,19 +89,6 @@ describe('class MockLink', () => {
8189
});
8290

8391
describe('method request', () => {
84-
const queryOneOperation = { query: queryOne, variables: { a: 'one' } } as Partial<Operation> as Operation;
85-
86-
const createMockObserver = (): jest.Mocked<Observer<any>> => ({
87-
next: jest.fn(),
88-
error: jest.fn(),
89-
complete: jest.fn(),
90-
});
91-
92-
it('throws when a handler is not defined for the query', () => {
93-
expect(() => mockLink.request(queryOneOperation))
94-
.toThrowError(`Request handler not defined for query: ${print(queryOne)}`)
95-
});
96-
9792
it('correctly executes the handler when the handler is defined as a promise and it and successfully resolves', async () => {
9893
const handler = jest.fn().mockResolvedValue({ data: 'Query one result' });
9994
mockLink.setRequestHandler(queryOne, handler);
@@ -242,4 +237,46 @@ describe('class MockLink', () => {
242237
expect(observer.complete).toBeCalledTimes(1);
243238
});
244239
});
240+
241+
describe('constructor option "missingHandlerPolicy"', () => {
242+
it('when "throw-error" throws when a handler is not defined for the query', () => {
243+
mockLink = new MockLink({missingHandlerPolicy: 'throw-error'})
244+
245+
expect(() => mockLink.request(queryOneOperation))
246+
.toThrowError(`Request handler not defined for query: ${print(queryOne)}`)
247+
});
248+
249+
it('when "warn-and-return-error" logs a warning when a handler is not defined for the query', async () => {
250+
mockLink = new MockLink({missingHandlerPolicy: 'warn-and-return-error'})
251+
252+
const observable = mockLink.request(queryOneOperation);
253+
const observer = createMockObserver();
254+
255+
observable.subscribe(observer);
256+
257+
await new Promise(r => setTimeout(r, 0));
258+
259+
expect(observer.next).not.toBeCalled();
260+
expect(observer.error).toBeCalled();
261+
expect(observer.complete).not.toBeCalled();
262+
expect(console.warn).toBeCalledTimes(1);
263+
expect(console.warn).toBeCalledWith(`Request handler not defined for query: ${print(queryOne)}`);
264+
});
265+
266+
it('when "return-error" returns an error when a handler is not defined for the query', async () => {
267+
mockLink = new MockLink({missingHandlerPolicy: 'return-error'})
268+
269+
const observable = mockLink.request(queryOneOperation);
270+
const observer = createMockObserver();
271+
272+
observable.subscribe(observer);
273+
274+
await new Promise(r => setTimeout(r, 0));
275+
276+
expect(observer.next).not.toBeCalled();
277+
expect(observer.error).toBeCalledTimes(1);
278+
expect(observer.error).toBeCalledWith(new Error(`Request handler not defined for query: ${print(queryOne)}`));
279+
expect(observer.complete).not.toBeCalled();
280+
});
281+
})
245282
});

src/mockLink.ts

+29-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,22 @@ import { RequestHandler, RequestHandlerResponse } from './mockClient';
44
import { removeClientSetsFromDocument, removeConnectionDirectiveFromDocument } from '@apollo/client/utilities';
55
import { IMockSubscription, MockSubscription } from './mockSubscription';
66

7+
export type MissingHandlerPolicy = 'throw-error' | 'warn-and-return-error' | 'return-error';
8+
9+
interface MockLinkOptions {
10+
missingHandlerPolicy?: MissingHandlerPolicy;
11+
}
12+
13+
const DEFAULT_MISSING_HANDLER_POLICY: MissingHandlerPolicy = 'throw-error';
14+
715
export class MockLink extends ApolloLink {
16+
constructor(options?: MockLinkOptions) {
17+
super();
18+
19+
this.missingHandlerPolicy = options?.missingHandlerPolicy || DEFAULT_MISSING_HANDLER_POLICY;
20+
}
21+
22+
private readonly missingHandlerPolicy: MissingHandlerPolicy;
823
private requestHandlers: Record<string, RequestHandler | undefined> = {};
924

1025
setRequestHandler(requestQuery: DocumentNode, handler: RequestHandler): void {
@@ -29,11 +44,19 @@ export class MockLink extends ApolloLink {
2944

3045
const handler = this.requestHandlers[key];
3146

32-
if (!handler) {
33-
throw new Error(`Request handler not defined for query: ${print(operation.query)}`);
47+
if (!handler && this.missingHandlerPolicy === 'throw-error') {
48+
throw new Error(getNotDefinedHandlerMessage(operation));
3449
}
3550

3651
return new Observable<FetchResult>(observer => {
52+
if (!handler) {
53+
if (this.missingHandlerPolicy === 'warn-and-return-error') {
54+
console.warn(getNotDefinedHandlerMessage(operation));
55+
}
56+
throw new Error(getNotDefinedHandlerMessage(operation));
57+
}
58+
59+
3760
let result:
3861
| Promise<RequestHandlerResponse<any>>
3962
| IMockSubscription<any>
@@ -101,3 +124,7 @@ const isPromise = (maybePromise: any): maybePromise is Promise<any> =>
101124

102125
const isSubscription = (maybeSubscription: any): maybeSubscription is MockSubscription<any> =>
103126
maybeSubscription && maybeSubscription instanceof MockSubscription;
127+
128+
const getNotDefinedHandlerMessage = (operation: Operation) => {
129+
return `Request handler not defined for query: ${print(operation.query)}`
130+
}

0 commit comments

Comments
 (0)