Skip to content

Commit

Permalink
Merge pull request #11806 from apollographql/pr/mockLink-defaults
Browse files Browse the repository at this point in the history
MockLink: add query default variables if not specified in mock request
  • Loading branch information
phryneas authored Jul 8, 2024
2 parents d88c7f8 + e9d26e5 commit 5ac68eb
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 7 deletions.
5 changes: 5 additions & 0 deletions .changeset/fluffy-badgers-rush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@apollo/client": patch
---

MockLink: add query default variables if not specified in mock request
84 changes: 80 additions & 4 deletions src/testing/core/mocking/__tests__/mockLink.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import gql from "graphql-tag";
import { MockLink, MockedResponse } from "../mockLink";
import { execute } from "../../../../link/core/execute";
import { ObservableStream } from "../../../internal";
import { ObservableStream, enableFakeTimers } from "../../../internal";

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

describe("mockLink", () => {
beforeAll(() => jest.useFakeTimers());
afterAll(() => jest.useRealTimers());

const query = gql`
query A {
a
}
`;

it("should not require a result or error when delay equals Infinity", async () => {
using _fakeTimers = enableFakeTimers();

const mockLink = new MockLink([
{
request: {
Expand All @@ -104,6 +103,8 @@ describe("mockLink", () => {
});

it("should require result or error when delay is just large", (done) => {
using _fakeTimers = enableFakeTimers();

const mockLink = new MockLink([
{
request: {
Expand All @@ -126,6 +127,81 @@ describe("mockLink", () => {

jest.advanceTimersByTime(MAXIMUM_DELAY);
});

it("should fill in default variables if they are missing in mocked requests", async () => {
const query = gql`
query GetTodo($done: Boolean = true, $user: String!) {
todo(user: $user, done: $done) {
id
}
}
`;
const mocks = [
{
// default should get filled in here
request: { query, variables: { user: "Tim" } },
result: {
data: { todo: { id: 1 } },
},
},
{
// we provide our own `done`, so it should not get filled in
request: { query, variables: { user: "Tim", done: false } },
result: {
data: { todo: { id: 2 } },
},
},
{
// one more that has a different user variable and should never match
request: { query, variables: { user: "Tom" } },
result: {
data: { todo: { id: 2 } },
},
},
];

// Apollo Client will always fill in default values for missing variables
// in the operation before calling the Link, so we have to do the same here
// when we call `execute`
const defaults = { done: true };
const link = new MockLink(mocks, false, { showWarnings: false });
{
// Non-optional variable is missing, should not match.
const stream = new ObservableStream(
execute(link, { query, variables: { ...defaults } })
);
await stream.takeError();
}
{
// Execute called incorrectly without a default variable filled in.
// This will never happen in Apollo Client since AC always fills these
// before calling `execute`, so it's okay if it results in a "no match"
// scenario here.
const stream = new ObservableStream(
execute(link, { query, variables: { user: "Tim" } })
);
await stream.takeError();
}
{
// Expect default value to be filled in the mock request.
const stream = new ObservableStream(
execute(link, { query, variables: { ...defaults, user: "Tim" } })
);
const result = await stream.takeNext();
expect(result).toEqual({ data: { todo: { id: 1 } } });
}
{
// Test that defaults don't overwrite explicitly different values in a mock request.
const stream = new ObservableStream(
execute(link, {
query,
variables: { ...defaults, user: "Tim", done: false },
})
);
const result = await stream.takeNext();
expect(result).toEqual({ data: { todo: { id: 2 } } });
}
});
});

test("removes @nonreactive directives from fields", async () => {
Expand Down
12 changes: 9 additions & 3 deletions src/testing/core/mocking/mockLink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import {
cloneDeep,
stringifyForDisplay,
print,
getOperationDefinition,
getDefaultValues,
removeDirectivesFromDocument,
checkDocument,
} from "../../../utilities/index.js";
Expand Down Expand Up @@ -233,17 +235,21 @@ ${unmatchedVars.map((d) => ` ${stringifyForDisplay(d)}`).join("\n")}
}

private normalizeVariableMatching(mockedResponse: MockedResponse) {
const variables = mockedResponse.request.variables;
if (mockedResponse.variableMatcher && variables) {
const request = mockedResponse.request;
if (mockedResponse.variableMatcher && request.variables) {
throw new Error(
"Mocked response should contain either variableMatcher or request.variables"
);
}

if (!mockedResponse.variableMatcher) {
request.variables = {
...getDefaultValues(getOperationDefinition(request.query)),
...request.variables,
};
mockedResponse.variableMatcher = (vars) => {
const requestVariables = vars || {};
const mockedResponseVariables = variables || {};
const mockedResponseVariables = request.variables || {};
return equal(requestVariables, mockedResponseVariables);
};
}
Expand Down
26 changes: 26 additions & 0 deletions src/testing/internal/disposables/enableFakeTimers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { withCleanup } from "./withCleanup.js";

declare global {
interface DateConstructor {
/* Jest uses @sinonjs/fake-timers, that add this flag */
isFake: boolean;
}
}

export function enableFakeTimers(
config?: FakeTimersConfig | LegacyFakeTimersConfig
) {
if (global.Date.isFake === true) {
// Nothing to do here, fake timers have already been set up.
// That also means we don't want to clean that up later.
return withCleanup({}, () => {});
}

jest.useFakeTimers(config);
return withCleanup({}, () => {
if (global.Date.isFake === true) {
jest.runOnlyPendingTimers();
jest.useRealTimers();
}
});
}
1 change: 1 addition & 0 deletions src/testing/internal/disposables/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { disableActWarnings } from "./disableActWarnings.js";
export { spyOnConsole } from "./spyOnConsole.js";
export { withCleanup } from "./withCleanup.js";
export { enableFakeTimers } from "./enableFakeTimers.js";

0 comments on commit 5ac68eb

Please sign in to comment.