Skip to content

Commit 8df6013

Browse files
committed
MockLink: add query default variables if not specified in mock request
resolves #8023
1 parent 31c3df4 commit 8df6013

File tree

5 files changed

+122
-3
lines changed

5 files changed

+122
-3
lines changed

.changeset/fluffy-badgers-rush.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@apollo/client": patch
3+
---
4+
5+
MockLink: add query default variables if not specified in mock request

src/testing/core/mocking/__tests__/mockLink.ts

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import gql from "graphql-tag";
22
import { MockLink, MockedResponse } from "../mockLink";
33
import { execute } from "../../../../link/core/execute";
4+
import { ObservableStream, enableFakeTimers } from "../../../internal";
45

56
describe("MockedResponse.newData", () => {
67
const setup = () => {
@@ -72,16 +73,15 @@ We've chosen this value as the MAXIMUM_DELAY since values that don't fit into a
7273
const MAXIMUM_DELAY = 0x7f_ff_ff_ff;
7374

7475
describe("mockLink", () => {
75-
beforeAll(() => jest.useFakeTimers());
76-
afterAll(() => jest.useRealTimers());
77-
7876
const query = gql`
7977
query A {
8078
a
8179
}
8280
`;
8381

8482
it("should not require a result or error when delay equals Infinity", async () => {
83+
using _fakeTimers = enableFakeTimers();
84+
8585
const mockLink = new MockLink([
8686
{
8787
request: {
@@ -103,6 +103,8 @@ describe("mockLink", () => {
103103
});
104104

105105
it("should require result or error when delay is just large", (done) => {
106+
using _fakeTimers = enableFakeTimers();
107+
106108
const mockLink = new MockLink([
107109
{
108110
request: {
@@ -125,4 +127,80 @@ describe("mockLink", () => {
125127

126128
jest.advanceTimersByTime(MAXIMUM_DELAY);
127129
});
130+
131+
it("should fill in default variables if they are missing in mocked requests", async () => {
132+
const query = gql`
133+
query GetTodo($done: Boolean = true, $user: String!) {
134+
todo(user: $user, done: $done) {
135+
id
136+
title
137+
}
138+
}
139+
`;
140+
const mocks = [
141+
{
142+
// default should get filled in here
143+
request: { query, variables: { user: "Tim" } },
144+
result: {
145+
data: { todo: { id: 1 } },
146+
},
147+
},
148+
{
149+
// we provide our own `done`, so it should not get filled in
150+
request: { query, variables: { user: "Tim", done: false } },
151+
result: {
152+
data: { todo: { id: 2 } },
153+
},
154+
},
155+
{
156+
// one more that has a different user variable and should never match
157+
request: { query, variables: { user: "Tom" } },
158+
result: {
159+
data: { todo: { id: 2 } },
160+
},
161+
},
162+
];
163+
164+
// Apollo Client will always fill in default values for missing variables
165+
// in the operation before calling the Link, so we have to do the same here
166+
// when we call `execute`
167+
const defaults = { done: true };
168+
const link = new MockLink(mocks, false, { showWarnings: false });
169+
{
170+
// Non-optional variable is missing, should not match.
171+
const stream = new ObservableStream(
172+
execute(link, { query, variables: { ...defaults } })
173+
);
174+
await stream.takeError();
175+
}
176+
{
177+
// Execute called incorrectly without a default variable filled in.
178+
// This will never happen in Apollo Client since AC always fills these
179+
// before calling `execute`, so it's okay if it results in a "no match"
180+
// scenario here.
181+
const stream = new ObservableStream(
182+
execute(link, { query, variables: { user: "Tim" } })
183+
);
184+
await stream.takeError();
185+
}
186+
{
187+
// Expect default value to be filled in the mock request.
188+
const stream = new ObservableStream(
189+
execute(link, { query, variables: { ...defaults, user: "Tim" } })
190+
);
191+
const result = await stream.takeNext();
192+
expect(result).toEqual({ data: { todo: { id: 1 } } });
193+
}
194+
{
195+
// Test that defaults don't overwrite explicitly different values in a mock request.
196+
const stream = new ObservableStream(
197+
execute(link, {
198+
query,
199+
variables: { ...defaults, user: "Tim", done: false },
200+
})
201+
);
202+
const result = await stream.takeNext();
203+
expect(result).toEqual({ data: { todo: { id: 2 } } });
204+
}
205+
});
128206
});

src/testing/core/mocking/mockLink.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import {
1717
cloneDeep,
1818
stringifyForDisplay,
1919
print,
20+
getOperationDefinition,
21+
getDefaultValues,
2022
} from "../../../utilities/index.js";
2123

2224
export type ResultFunction<T, V = Record<string, any>> = (variables: V) => T;
@@ -212,6 +214,13 @@ ${unmatchedVars.map((d) => ` ${stringifyForDisplay(d)}`).join("\n")}
212214
newMockedResponse.request.query = query;
213215
}
214216

217+
newMockedResponse.request.variables = {
218+
...getDefaultValues(
219+
getOperationDefinition(newMockedResponse.request.query)
220+
),
221+
...newMockedResponse.request.variables,
222+
};
223+
215224
mockedResponse.maxUsageCount = mockedResponse.maxUsageCount ?? 1;
216225
invariant(
217226
mockedResponse.maxUsageCount > 0,
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { withCleanup } from "./withCleanup.js";
2+
3+
declare global {
4+
interface DateConstructor {
5+
/* Jest uses @sinonjs/fake-timers, that add this flag */
6+
isFake: boolean;
7+
}
8+
}
9+
10+
export function enableFakeTimers(
11+
config?: FakeTimersConfig | LegacyFakeTimersConfig
12+
) {
13+
if (global.Date.isFake === true) {
14+
// Nothing to do here, fake timers have already been set up.
15+
// That also means we don't want to clean that up later.
16+
return withCleanup({}, () => {});
17+
}
18+
19+
jest.useFakeTimers(config);
20+
return withCleanup({}, () => {
21+
if (global.Date.isFake === true) {
22+
jest.runOnlyPendingTimers();
23+
jest.useRealTimers();
24+
}
25+
});
26+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export { disableActWarnings } from "./disableActWarnings.js";
22
export { spyOnConsole } from "./spyOnConsole.js";
33
export { withCleanup } from "./withCleanup.js";
4+
export { enableFakeTimers } from "./enableFakeTimers.js";

0 commit comments

Comments
 (0)