@zendesk/laika
Version:
Test, mock, intercept and modify Apollo Client's operations — in both browser and unit tests!
259 lines • 13.3 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const graphql_tag_1 = __importDefault(require("graphql-tag"));
const wait_for_observables_1 = __importDefault(require("wait-for-observables"));
const core_1 = require("@apollo/client/core");
const constants_1 = require("./constants");
const laika_1 = require("./laika");
const testUtils_1 = require("./testUtils");
const query = (0, graphql_tag_1.default) `
query helloQuery {
sample {
id
}
}
`;
const goodbyeQuery = (0, graphql_tag_1.default) `
query goodbyeQuery {
sample {
id
}
}
`;
const subscription = (0, graphql_tag_1.default) `
subscription helloSubscription {
sample {
id
}
}
`;
const standardError = new Error('I never work');
const data = { data: { hello: 'world' } };
const mockData = { data: { goodbye: 'world' } };
const mockDataImmediate = { data: { so: 'fast' } };
describe('Laika', () => {
it('returns passthrough data from the following link', () => __awaiter(void 0, void 0, void 0, function* () {
const laika = new laika_1.Laika({
referenceName: constants_1.DEFAULT_GLOBAL_PROPERTY_NAME,
});
const interceptionLink = laika.createLink();
const backendStub = jest.fn(() => core_1.Observable.of(data));
const link = core_1.ApolloLink.from([interceptionLink, backendStub]);
const [result] = (yield (0, wait_for_observables_1.default)((0, core_1.execute)(link, { query })));
const { values } = result;
expect(values).toEqual([data]);
expect(backendStub).toHaveBeenCalledTimes(1);
}));
describe('Intercept API', () => {
it('returns mocked data and does not connect to the following link', () => __awaiter(void 0, void 0, void 0, function* () {
const laika = new laika_1.Laika({
referenceName: constants_1.DEFAULT_GLOBAL_PROPERTY_NAME,
});
const interceptionLink = laika.createLink();
const backendStub = jest.fn(() => core_1.Observable.of(data));
const link = core_1.ApolloLink.from([interceptionLink, backendStub]);
const interceptor = laika.intercept();
interceptor.mockResultOnce({
result: mockData,
});
const [result] = (yield (0, wait_for_observables_1.default)((0, core_1.execute)(link, { query })));
const { values } = result;
expect(values).toEqual([mockData]);
expect(backendStub).toHaveBeenCalledTimes(0);
}));
it('returns mock once and then falls back to the following link - twice in a row', () => __awaiter(void 0, void 0, void 0, function* () {
const laika = new laika_1.Laika({
referenceName: constants_1.DEFAULT_GLOBAL_PROPERTY_NAME,
});
const interceptionLink = laika.createLink();
const backendStub = jest.fn(() => core_1.Observable.of(data));
const link = core_1.ApolloLink.from([interceptionLink, backendStub]);
const interceptor = laika.intercept();
let triedCount = 0;
while (++triedCount <= 2) {
interceptor.mockResultOnce({
result: mockData,
});
// eslint-disable-next-line no-await-in-loop
const [result1, result2] = (yield (0, wait_for_observables_1.default)((0, core_1.execute)(link, { query }), (0, core_1.execute)(link, { query })));
const { values: mockValues } = result1;
const { values: remoteValues } = result2;
expect(mockValues).toEqual([mockData]);
expect(remoteValues).toEqual([data]);
expect(backendStub).toHaveBeenCalledTimes(triedCount);
}
}));
it('connects to a mocked subscription without connecting to the following link and immediately fires mocked data', () => __awaiter(void 0, void 0, void 0, function* () {
const laika = new laika_1.Laika({
referenceName: constants_1.DEFAULT_GLOBAL_PROPERTY_NAME,
});
const interceptionLink = laika.createLink();
const mockedResultFn = jest.fn(() => ({ result: mockDataImmediate }));
const backendStub = jest.fn(() => core_1.Observable.of(data));
const link = core_1.ApolloLink.from([interceptionLink, backendStub]);
const interceptor = laika.intercept();
// testing that this will get pushed immediately
interceptor.mockResultOnce(mockedResultFn);
const observer = {
next: jest.fn(),
complete: jest.fn(),
error: jest.fn(),
};
const sub = (0, core_1.execute)(link, { query: subscription }).subscribe(observer);
expect.assertions(7);
yield (0, testUtils_1.onNextTick)(() => {
expect(mockedResultFn).toHaveBeenCalledTimes(1);
expect(observer.next).toHaveBeenCalledTimes(1);
expect(observer.next).toHaveBeenCalledWith(mockDataImmediate);
expect(observer.complete).not.toHaveBeenCalled();
expect(backendStub).toHaveBeenCalledTimes(0);
sub.unsubscribe();
expect(observer.complete).not.toHaveBeenCalled();
expect(observer.error).not.toHaveBeenCalled();
});
}));
it('connects to a mocked subscription without connecting to the following link, then fires a mock update', () => __awaiter(void 0, void 0, void 0, function* () {
const laika = new laika_1.Laika({
referenceName: constants_1.DEFAULT_GLOBAL_PROPERTY_NAME,
});
const interceptionLink = laika.createLink();
const backendStub = jest.fn(() => core_1.Observable.of(data));
const link = core_1.ApolloLink.from([interceptionLink, backendStub]);
const interceptor = laika.intercept();
const observer = {
next: jest.fn(),
complete: jest.fn(),
error: jest.fn(),
};
expect.assertions(7);
const sub = (0, core_1.execute)(link, { query: subscription }).subscribe(observer);
yield (0, testUtils_1.onNextTick)(() => {
expect(observer.next).not.toHaveBeenCalled();
interceptor.fireSubscriptionUpdate({ result: mockData });
expect(observer.next).toHaveBeenCalledTimes(1);
expect(observer.next).toHaveBeenCalledWith(mockData);
expect(observer.complete).not.toHaveBeenCalled();
expect(backendStub).toHaveBeenCalledTimes(0);
sub.unsubscribe();
expect(observer.complete).not.toHaveBeenCalled();
expect(observer.error).not.toHaveBeenCalled();
});
}));
it('waitForActiveSubscription generates a Promise when no current active subscription, which resolves once one is made', () => __awaiter(void 0, void 0, void 0, function* () {
const laika = new laika_1.Laika({
referenceName: constants_1.DEFAULT_GLOBAL_PROPERTY_NAME,
});
const interceptionLink = laika.createLink();
const backendStub = jest.fn(() => core_1.Observable.of(data));
const link = core_1.ApolloLink.from([interceptionLink, backendStub]);
const interceptor = laika.intercept();
const observer = {
next: jest.fn(),
complete: jest.fn(),
error: jest.fn(),
};
expect.assertions(3);
const hasSettled = jest.fn();
const waitPromise = interceptor.waitForActiveSubscription();
expect(waitPromise).toBeInstanceOf(Promise);
void waitPromise.then(hasSettled);
yield (0, testUtils_1.onNextTick)(() => {
expect(hasSettled).not.toHaveBeenCalled();
});
const sub = (0, core_1.execute)(link, { query: subscription }).subscribe(observer);
yield (0, testUtils_1.onNextTick)(() => {
expect(hasSettled).toHaveBeenCalled();
sub.unsubscribe();
});
}));
describe('intercept with a matcher', () => {
it.each([
['MatcherObject (operationName)', { operationName: 'goodbyeQuery' }],
['MatcherObject (variables)', { variables: { type: 'goodbye' } }],
[
'MatcherFn',
(operation) => operation.operationName === 'goodbyeQuery',
],
])('correctly intercepts only operations matched by %s and leaves other alone', (_, matcher) => __awaiter(void 0, void 0, void 0, function* () {
const laika = new laika_1.Laika({
referenceName: constants_1.DEFAULT_GLOBAL_PROPERTY_NAME,
});
const interceptionLink = laika.createLink();
const backendStub = jest.fn(() => core_1.Observable.of(data));
const link = core_1.ApolloLink.from([interceptionLink, backendStub]);
const interceptor = laika.intercept(matcher);
interceptor.mockResultOnce({
result: mockData,
});
const [result1, result2] = (yield (0, wait_for_observables_1.default)((0, core_1.execute)(link, { query }), (0, core_1.execute)(link, {
query: goodbyeQuery,
variables: { type: 'goodbye' },
})));
const { values } = result1;
const { values: goodbyeValues } = result2;
expect(values).toEqual([data]);
expect(goodbyeValues).toEqual([mockData]);
expect(backendStub).toHaveBeenCalledTimes(1);
}));
});
});
it('calls unsubscribe on the appropriate downstream observable', () => __awaiter(void 0, void 0, void 0, function* () {
const laika = new laika_1.Laika({
referenceName: constants_1.DEFAULT_GLOBAL_PROPERTY_NAME,
});
const interceptionLink = laika.createLink();
const unsubscribeStub = jest.fn();
// Hold the test hostage until we're hit
let underlyingObservable;
const untilSubscribed = new Promise((resolve) => {
underlyingObservable = {
subscribe(observer) {
resolve(undefined); // Release hold on test.
void Promise.resolve().then(() => {
observer.next(data);
observer.complete();
});
return { unsubscribe: unsubscribeStub, closed: false };
},
};
});
const backendStub = jest.fn();
backendStub.mockReturnValueOnce(underlyingObservable);
const link = core_1.ApolloLink.from([interceptionLink, backendStub]);
// eslint-disable-next-line @typescript-eslint/no-shadow
const subscription = (0, core_1.execute)(link, { query }).subscribe({});
yield untilSubscribed;
subscription.unsubscribe();
expect(unsubscribeStub).toHaveBeenCalledTimes(1);
}));
it('supports multiple subscribers to the same request', () => __awaiter(void 0, void 0, void 0, function* () {
const laika = new laika_1.Laika({
referenceName: constants_1.DEFAULT_GLOBAL_PROPERTY_NAME,
});
const interceptionLink = laika.createLink();
const stub = jest.fn();
stub.mockReturnValueOnce((0, core_1.fromError)(standardError));
stub.mockReturnValueOnce((0, core_1.fromError)(standardError));
stub.mockReturnValueOnce(core_1.Observable.of(data));
const link = core_1.ApolloLink.from([interceptionLink, stub]);
const observable = (0, core_1.execute)(link, { query });
const [result1, result2, result3] = (yield (0, wait_for_observables_1.default)(observable, observable, observable));
expect(result1).toEqual({ error: standardError });
expect(result2).toEqual({ error: standardError });
expect(result3.values).toEqual([data]);
expect(stub).toHaveBeenCalledTimes(3);
}));
});
//# sourceMappingURL=laika.test.js.map