From 01ca118ffc984fca930838d942e41fbb68c66ffe Mon Sep 17 00:00:00 2001 From: Stewart Parry Date: Mon, 14 Aug 2023 13:25:47 +0000 Subject: [PATCH] Allow merging of mocked queries in GraphQL update --- .changeset/perfect-pigs-heal.md | 5 ++ .../graphql-testing/src/graphql-controller.ts | 9 ++- packages/graphql-testing/src/links/mocks.ts | 15 +++- .../graphql-testing/src/tests/e2e.test.tsx | 76 +++++++++++++++++++ packages/graphql-testing/src/types.ts | 9 ++- 5 files changed, 109 insertions(+), 5 deletions(-) create mode 100644 .changeset/perfect-pigs-heal.md diff --git a/.changeset/perfect-pigs-heal.md b/.changeset/perfect-pigs-heal.md new file mode 100644 index 0000000000..9bcdf8f8f4 --- /dev/null +++ b/.changeset/perfect-pigs-heal.md @@ -0,0 +1,5 @@ +--- +'@shopify/graphql-testing': minor +--- + +Added `updateMockWithMerge` to `GraphQLController` to allow a partial update of mocked queries. diff --git a/packages/graphql-testing/src/graphql-controller.ts b/packages/graphql-testing/src/graphql-controller.ts index 8bc47942a7..9ef1641028 100644 --- a/packages/graphql-testing/src/graphql-controller.ts +++ b/packages/graphql-testing/src/graphql-controller.ts @@ -4,7 +4,7 @@ import {ApolloClient, ApolloLink, InMemoryCache} from '@apollo/client'; import {MockLink, InflightLink} from './links'; import {Operations} from './operations'; import {operationNameFromFindOptions} from './utilities'; -import type {GraphQLMock, MockRequest, FindOptions} from './types'; +import type {GraphQLMock, MockGraphQLObject, MockRequest, FindOptions} from './types'; export interface Options { cacheOptions?: InMemoryCacheConfig; @@ -56,6 +56,13 @@ export class GraphQL { this.mockLink.updateMock(mock); } + updateWithMerge(mock: MockGraphQLObject) { + if (!this.mockLink) { + return; + } + this.mockLink.updateMockWithMerge(mock); + } + async resolveAll(options: ResolveAllFindOptions = {}) { let requestFilter: ((operation: MockRequest) => boolean) | undefined; diff --git a/packages/graphql-testing/src/links/mocks.ts b/packages/graphql-testing/src/links/mocks.ts index c4b373c5a6..a57132abd2 100644 --- a/packages/graphql-testing/src/links/mocks.ts +++ b/packages/graphql-testing/src/links/mocks.ts @@ -3,7 +3,11 @@ import {ApolloLink, Observable} from '@apollo/client'; import type {ExecutionResult} from 'graphql'; import {GraphQLError} from 'graphql'; -import type {GraphQLMock, MockGraphQLFunction} from '../types'; +import type { + GraphQLMock, + MockGraphQLFunction, + MockGraphQLObject, +} from '../types'; export class MockLink extends ApolloLink { constructor(private mock: GraphQLMock) { @@ -14,6 +18,15 @@ export class MockLink extends ApolloLink { this.mock = mock; } + updateMockWithMerge(mock: MockGraphQLObject) { + if (typeof this.mock === 'function') { + throw new Error( + "Can't merge a GraphQL function mock with a GraphQL object mock", + ); + } + this.mock = {...this.mock, ...mock}; + } + request(operation: Operation): Observable { return new Observable((obs) => { const {mock} = this; diff --git a/packages/graphql-testing/src/tests/e2e.test.tsx b/packages/graphql-testing/src/tests/e2e.test.tsx index 79c227b5d4..3f589bcff9 100644 --- a/packages/graphql-testing/src/tests/e2e.test.tsx +++ b/packages/graphql-testing/src/tests/e2e.test.tsx @@ -344,6 +344,82 @@ describe('graphql-testing', () => { expect(myComponent).toContainReactText('Garfield2'); }); + it('allows for merged mock updates after it has been initialized', async () => { + const MyComponentWithTwoQueries = () => { + const queryResult = useQuery(personQuery, {variables: {id: '1'}}); + const {data, refetch} = queryResult; + + return ( + <> +
{data?.person?.name}
{' '} + + + + ); + }; + + const mockPetQueryData = {pet: {__typename: 'Cat', name: 'Garfield'}}; + + const graphQL = createGraphQL({ + Pet: mockPetQueryData, + }); + + const mockPersonQueryData = { + person: {__typename: 'Person', name: 'Jon Arbuckle'}, + }; + + graphQL.updateWithMerge({ + Person: mockPersonQueryData, + }); + + const myComponent = mount( + + + , + ); + + graphQL.wrap((resolve) => myComponent.act(resolve)); + await graphQL.resolveAll(); + + expect(myComponent).toContainReactText('Garfield'); + expect(myComponent).toContainReactText('Jon Arbuckle'); + + const newMockPetQueryData = {pet: {__typename: 'Cat', name: 'Garfield2'}}; + graphQL.updateWithMerge({ + // Partial update of pet mock + Pet: newMockPetQueryData, + }); + + const request = myComponent + .find('button', {children: 'Refetch!'})! + .trigger('onClick'); + await graphQL.resolveAll(); + await request; + + expect(myComponent).toContainReactText('Garfield2'); + expect(myComponent).toContainReactText('Jon Arbuckle'); + + const newMockPersonQueryData = { + person: {__typename: 'Person', name: 'Jon Arbuckle Jr.'}, + }; + + graphQL.updateWithMerge({ + // Partial update of person mock + Person: newMockPersonQueryData, + }); + + const personRequest = myComponent + .find('button', {children: 'Refetch Person'})! + .trigger('onClick'); + await graphQL.resolveAll(); + await personRequest; + + expect(myComponent).toContainReactText('Garfield2'); + expect(myComponent).toContainReactText('Jon Arbuckle Jr.'); + }); + it('handles fetchMore', async () => { const graphQL = createGraphQL({ Pets: ({ diff --git a/packages/graphql-testing/src/types.ts b/packages/graphql-testing/src/types.ts index f219b2ef80..9eb7ebd4a8 100644 --- a/packages/graphql-testing/src/types.ts +++ b/packages/graphql-testing/src/types.ts @@ -10,9 +10,12 @@ export type MockGraphQLResponse = Error | object; export type MockGraphQLFunction = ( request: GraphQLRequest, ) => MockGraphQLResponse; -export type GraphQLMock = - | {[key: string]: MockGraphQLResponse | MockGraphQLFunction} - | MockGraphQLFunction; + +export type MockGraphQLObject = { + [key: string]: MockGraphQLResponse | MockGraphQLFunction; +}; + +export type GraphQLMock = MockGraphQLObject | MockGraphQLFunction; export interface MockRequest { operation: Operation;