Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature request 241: reuse mocks in MockLink / MockedProvider #11178

Merged
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .api-reports/api-report-react.md
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,8 @@ export interface BackgroundQueryHookOptions<TData = unknown, TVariables extends
queryKey?: string | number | any[];
// (undocumented)
skip?: boolean;
// (undocumented)
suspenseCache?: SuspenseCache;
}

// @public (undocumented)
Expand Down Expand Up @@ -1915,6 +1917,27 @@ export interface SubscriptionResult<TData = any, TVariables = any> {
variables?: TVariables;
}

// Warning: (ae-forgotten-export) The symbol "SuspenseCache_2" needs to be exported by the entry point index.d.ts
//
// @public (undocumented)
export class SuspenseCache extends SuspenseCache_2 {
constructor();
}

// @public (undocumented)
class SuspenseCache_2 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused as to why these SuspenseCache updates are here @phryneas - this branch has your recent commit that landed on main that removed it... I'm going to merge this PR anyways and we can address this elsewhere if it's unintended, but wanted to flag it 👍

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah whoops, I had an old version in dist and didn't realize. Made a small tweak to the script so it's impossible to make this mistake again :) 1fc9df6

// Warning: (ae-forgotten-export) The symbol "SuspenseCacheOptions" needs to be exported by the entry point index.d.ts
constructor(options?: SuspenseCacheOptions);
// (undocumented)
getQueryRef<TData = any>(cacheKey: CacheKey, createObservable: () => ObservableQuery<TData>): InternalQueryReference<TData>;
}

// @public (undocumented)
interface SuspenseCacheOptions {
// (undocumented)
autoDisposeTimeoutMs?: number;
}

// @public (undocumented)
export type SuspenseQueryHookFetchPolicy = Extract<WatchQueryFetchPolicy, "cache-first" | "network-only" | "no-cache" | "cache-and-network">;

Expand All @@ -1926,6 +1949,8 @@ export interface SuspenseQueryHookOptions<TData = unknown, TVariables extends Op
queryKey?: string | number | any[];
// (undocumented)
skip?: boolean;
// (undocumented)
suspenseCache?: SuspenseCache;
}

// @public (undocumented)
Expand Down
27 changes: 27 additions & 0 deletions .api-reports/api-report-react_hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,10 @@ interface BackgroundQueryHookOptions<TData = unknown, TVariables extends Operati
queryKey?: string | number | any[];
// (undocumented)
skip?: boolean;
// Warning: (ae-forgotten-export) The symbol "SuspenseCache" needs to be exported by the entry point index.d.ts
//
// (undocumented)
suspenseCache?: SuspenseCache;
}

// Warning: (ae-forgotten-export) The symbol "BackgroundQueryHookOptions" needs to be exported by the entry point index.d.ts
Expand Down Expand Up @@ -1794,6 +1798,27 @@ interface SubscriptionResult<TData = any, TVariables = any> {
variables?: TVariables;
}

// Warning: (ae-forgotten-export) The symbol "SuspenseCache_2" needs to be exported by the entry point index.d.ts
//
// @public (undocumented)
class SuspenseCache extends SuspenseCache_2 {
constructor();
}

// @public (undocumented)
class SuspenseCache_2 {
// Warning: (ae-forgotten-export) The symbol "SuspenseCacheOptions" needs to be exported by the entry point index.d.ts
constructor(options?: SuspenseCacheOptions);
// (undocumented)
getQueryRef<TData = any>(cacheKey: CacheKey, createObservable: () => ObservableQuery<TData>): InternalQueryReference<TData>;
}

// @public (undocumented)
interface SuspenseCacheOptions {
// (undocumented)
autoDisposeTimeoutMs?: number;
}

// @public (undocumented)
type SuspenseQueryHookFetchPolicy = Extract<WatchQueryFetchPolicy, "cache-first" | "network-only" | "no-cache" | "cache-and-network">;

Expand All @@ -1807,6 +1832,8 @@ interface SuspenseQueryHookOptions<TData = unknown, TVariables extends Operation
queryKey?: string | number | any[];
// (undocumented)
skip?: boolean;
// (undocumented)
suspenseCache?: SuspenseCache;
}

// @public (undocumented)
Expand Down
25 changes: 25 additions & 0 deletions .api-reports/api-report.md
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,8 @@ export interface BackgroundQueryHookOptions<TData = unknown, TVariables extends
queryKey?: string | number | any[];
// (undocumented)
skip?: boolean;
// (undocumented)
suspenseCache?: SuspenseCache;
}

// @public (undocumented)
Expand Down Expand Up @@ -2527,6 +2529,27 @@ export interface SubscriptionResult<TData = any, TVariables = any> {
variables?: TVariables;
}

// Warning: (ae-forgotten-export) The symbol "SuspenseCache_2" needs to be exported by the entry point index.d.ts
//
// @public (undocumented)
export class SuspenseCache extends SuspenseCache_2 {
constructor();
}

// @public (undocumented)
class SuspenseCache_2 {
// Warning: (ae-forgotten-export) The symbol "SuspenseCacheOptions" needs to be exported by the entry point index.d.ts
constructor(options?: SuspenseCacheOptions);
// (undocumented)
getQueryRef<TData = any>(cacheKey: CacheKey, createObservable: () => ObservableQuery<TData>): InternalQueryReference<TData>;
}

// @public (undocumented)
interface SuspenseCacheOptions {
// (undocumented)
autoDisposeTimeoutMs?: number;
}

// @public (undocumented)
export type SuspenseQueryHookFetchPolicy = Extract<WatchQueryFetchPolicy, "cache-first" | "network-only" | "no-cache" | "cache-and-network">;

Expand All @@ -2538,6 +2561,8 @@ export interface SuspenseQueryHookOptions<TData = unknown, TVariables extends Op
queryKey?: string | number | any[];
// (undocumented)
skip?: boolean;
// (undocumented)
suspenseCache?: SuspenseCache;
}

// @public (undocumented)
Expand Down
5 changes: 5 additions & 0 deletions .changeset/yellow-flies-repeat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@apollo/client": minor
---

Support re-using of mocks in the MockedProvider
31 changes: 31 additions & 0 deletions docs/source/development-testing/testing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,37 @@ it("renders without error", async () => {

</ExpansionPanel>

#### Reusing mocks

By default, a mock is only used once. If you want to reuse a mock for multiple operations, you can set the `maxUsageCount` field to a number indicating how many times the mock should be used:

<ExpansionPanel title="Click to expand 🐶">

```jsx title="dog.test.js"
import { GET_DOG_QUERY } from "./dog";

const mocks = [
{
request: {
query: GET_DOG_QUERY,
variables: {
name: "Buck"
}
},
result: {
data: {
dog: { id: "1", name: "Buck", breed: "bulldog" }
}
},
maxUsageCount: 2, // The mock can be used twice before it's removed, default is 1
}
];
```

</ExpansionPanel>

Passing `Number.POSITIVE_INFINITY` will cause the mock to be reused indefinitely.

### Dynamic variables

Sometimes, the exact value of the variables being passed are not known. The `MockedResponse` object takes a `variableMatcher` property that is a function that takes the variables and returns a boolean indication if this mock should match the invocation for the provided query. You cannot specify this parameter and `request.variables` at the same time.
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 14 additions & 2 deletions src/testing/core/mocking/mockLink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export interface MockedResponse<
TVariables = Record<string, any>,
> {
request: GraphQLRequest<TVariables>;
maxUsageCount?: number;
result?: FetchResult<TData> | ResultFunction<FetchResult<TData>, TVariables>;
error?: Error;
delay?: number;
Expand Down Expand Up @@ -135,8 +136,11 @@ ${unmatchedVars.map((d) => ` ${stringifyForDisplay(d)}`).join("\n")}
);
}
} else {
mockedResponses.splice(responseIndex, 1);

if (response.maxUsageCount! > 1) {
response.maxUsageCount!--;
} else {
mockedResponses.splice(responseIndex, 1);
}
const { newData } = response;
if (newData) {
response.result = newData(operation.variables);
Expand Down Expand Up @@ -203,6 +207,14 @@ ${unmatchedVars.map((d) => ` ${stringifyForDisplay(d)}`).join("\n")}
if (query) {
newMockedResponse.request.query = query;
}

mockedResponse.maxUsageCount = mockedResponse.maxUsageCount ?? 1;
invariant(
mockedResponse.maxUsageCount > 0,
`Mock response maxUsageCount must be greater than 0, %s given`,
mockedResponse.maxUsageCount
);

this.normalizeVariableMatching(newMockedResponse);
return newMockedResponse;
}
Expand Down
Loading
Loading