Skip to content

Commit c04672c

Browse files
committed
introduce 'missingHandlerPolicy' argument
1 parent 4e8e55f commit c04672c

4 files changed

+91
-54
lines changed

src/mockClient.integration.test.ts

+6-26
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { ApolloQueryResult, gql, InMemoryCache } from '@apollo/client/core';
55

66
import { createMockClient, MockApolloClient } from './mockClient';
77
import { createMockSubscription, IMockSubscription } from './mockSubscription';
8-
import { print } from "graphql";
98

109
describe('MockClient integration tests', () => {
1110
let mockClient: MockApolloClient;
@@ -56,10 +55,9 @@ describe('MockClient integration tests', () => {
5655
});
5756

5857
describe('Given request handler is not defined', () => {
59-
it('returns a promise which rejects due to handler not being defined', async () => {
60-
let promise = mockClient.query({ query: queryTwo });
61-
62-
await expect(promise).rejects.toThrowError('Request handler not defined for query');
58+
it('throws when executing the query', () => {
59+
expect(() => mockClient.query({ query: queryTwo }))
60+
.toThrowError('Request handler not defined for query');
6361
});
6462
});
6563
});
@@ -543,27 +541,9 @@ describe('MockClient integration tests', () => {
543541
});
544542

545543
describe('Given request handler is not defined', () => {
546-
it('returns an observable which returns error if handler not defined for query', async () => {
547-
let onNext = jest.fn();
548-
let onError = jest.fn();
549-
let onComplete = jest.fn();
550-
551-
const observable = mockClient.subscribe({ query: queryTwo });
552-
553-
observable.subscribe(
554-
onNext,
555-
onError,
556-
onComplete
557-
);
558-
559-
await new Promise(r => setTimeout(r, 0));
560-
561-
mockSubscription.next({ data: { one: 'A' } });
562-
563-
expect(onNext).not.toHaveBeenCalled();
564-
expect(onError).toHaveBeenCalledWith(new Error(`Request handler not defined for query: ${print(queryTwo)}`));
565-
expect(onError).toHaveBeenCalledTimes(1);
566-
expect(onComplete).not.toHaveBeenCalled();
544+
it('throws when attempting to subscribe to query', () => {
545+
expect(() => mockClient.subscribe({ query: queryTwo }))
546+
.toThrowError('Request handler not defined for query');
567547
});
568548
});
569549
});

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 LocalOptions {
19+
missingHandlerPolicy?: MissingHandlerPolicy;
20+
}
21+
22+
export type MockApolloClientOptions = Partial<Omit<ApolloClientOptions<NormalizedCacheObject>, 'link'>> & LocalOptions | undefined;
1923

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

src/mockLink.test.ts

+48-22
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,28 +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('returns an error when a handler is not defined for the query', async () => {
93-
const observable = mockLink.request(queryOneOperation);
94-
const observer = createMockObserver();
95-
96-
observable.subscribe(observer);
97-
98-
await new Promise(r => setTimeout(r, 0));
99-
100-
expect(observer.next).not.toBeCalled();
101-
expect(observer.error).toBeCalledTimes(1);
102-
expect(observer.error).toBeCalledWith(new Error(`Request handler not defined for query: ${print(queryOne)}`));
103-
expect(observer.complete).not.toBeCalled();
104-
});
105-
10692
it('correctly executes the handler when the handler is defined as a promise and it and successfully resolves', async () => {
10793
const handler = jest.fn().mockResolvedValue({ data: 'Query one result' });
10894
mockLink.setRequestHandler(queryOne, handler);
@@ -251,4 +237,44 @@ describe('class MockLink', () => {
251237
expect(observer.complete).toBeCalledTimes(1);
252238
});
253239
});
240+
241+
describe('constructor option "missingHandlerPolicy"', () => {
242+
it('when "error" throws when a handler is not defined for the query', () => {
243+
expect(() => mockLink.request(queryOneOperation))
244+
.toThrowError(`Request handler not defined for query: ${print(queryOne)}`)
245+
});
246+
247+
it('when "warn" logs a warning when a handler is not defined for the query', async () => {
248+
mockLink = new MockLink({missingHandlerPolicy: 'warn-and-return-error'})
249+
250+
const observable = mockLink.request(queryOneOperation);
251+
const observer = createMockObserver();
252+
253+
observable.subscribe(observer);
254+
255+
await new Promise(r => setTimeout(r, 0));
256+
257+
expect(observer.next).not.toBeCalled();
258+
expect(observer.error).toBeCalled();
259+
expect(observer.complete).not.toBeCalled();
260+
expect(console.warn).toBeCalledTimes(1);
261+
expect(console.warn).toBeCalledWith(`Request handler not defined for query: ${print(queryOne)}`);
262+
});
263+
264+
it('when "ignore" returns an error when a handler is not defined for the query', async () => {
265+
mockLink = new MockLink({missingHandlerPolicy: 'return-error'})
266+
267+
const observable = mockLink.request(queryOneOperation);
268+
const observer = createMockObserver();
269+
270+
observable.subscribe(observer);
271+
272+
await new Promise(r => setTimeout(r, 0));
273+
274+
expect(observer.next).not.toBeCalled();
275+
expect(observer.error).toBeCalledTimes(1);
276+
expect(observer.error).toBeCalledWith(new Error(`Request handler not defined for query: ${print(queryOne)}`));
277+
expect(observer.complete).not.toBeCalled();
278+
});
279+
})
254280
});

src/mockLink.ts

+31-4
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 {
@@ -25,15 +40,23 @@ export class MockLink extends ApolloLink {
2540
}
2641

2742
request = (operation: Operation) => {
28-
return new Observable<FetchResult>(observer => {
29-
const key = requestToKey(operation.query);
43+
const key = requestToKey(operation.query);
44+
45+
const handler = this.requestHandlers[key];
3046

31-
const handler = this.requestHandlers[key];
47+
if (!handler && this.missingHandlerPolicy === 'throw-error') {
48+
throw new Error(getNotDefinedHandlerMessage(operation));
49+
}
3250

51+
return new Observable<FetchResult>(observer => {
3352
if (!handler) {
34-
throw new Error(`Request handler not defined for query: ${print(operation.query)}`);
53+
if (this.missingHandlerPolicy === 'warn-and-return-error') {
54+
console.warn(getNotDefinedHandlerMessage(operation));
55+
}
56+
throw new Error(getNotDefinedHandlerMessage(operation));
3557
}
3658

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)