0% found this document useful (0 votes)
30 views48 pages

Sophisticated Schema Mocking

The document discusses sophisticated schema mocking for GraphQL. It describes how the speaker migrated Coinbase from REST to GraphQL and the challenges of mocking GraphQL queries in tests. It then outlines an approach to schema mocking that allows: - Queries to be automatically mocked without breaking tests when new queries/fields are added. - Easy access to query variables in mocks. - Mocking individual fields to test specific scenarios. Examples are provided of implementing this approach with a query-level mocker middleware and field-level mock data overrides. Benefits include fully automated mocking of queries and flexibility to mock specific fields.

Uploaded by

xm6qi4v7nv9
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
30 views48 pages

Sophisticated Schema Mocking

The document discusses sophisticated schema mocking for GraphQL. It describes how the speaker migrated Coinbase from REST to GraphQL and the challenges of mocking GraphQL queries in tests. It then outlines an approach to schema mocking that allows: - Queries to be automatically mocked without breaking tests when new queries/fields are added. - Easy access to query variables in mocks. - Mocking individual fields to test specific scenarios. Examples are provided of implementing this approach with a query-level mocker middleware and field-level mock data overrides. Benefits include fully automated mocking of queries and flexibility to mock specific fields.

Uploaded by

xm6qi4v7nv9
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 48

Sophisticated Schema Mocking

Stephanie Saunders, Coinbase

#GraphQLConf
(2.5 yrs)
IC => Engineering Manager (5 mo)
● Developer Experience Org (velocity and quality)
○ Customers == Coinbase Engineers
● Data Layer Team (full stack)
○ GraphQL Service and Schema
○ @cbhq/data-layer
■ Wrapper around our GraphQL client
● Developer Experience Org (velocity and quality)
○ Customers == Coinbase Engineers
● Data Layer Team (full stack)
○ GraphQL Service and Schema
○ @cbhq/data-layer
■ Wrapper around our GraphQL client
● Migration from REST to GraphQL
○ Defining tools and best practices
○ Biggest customer complaint:
■ Mocking GraphQL in tests (Unit/E2E)
import { useLazyLoadQuery } from '@cbhq/data-layer';

const { viewer } = useLazyLoadQuery<UserPropertiesQuery>(


graphql`
query UserPropertiesQuery {
viewer {
userProperties {
name
}
}
}
`,
{},
);
it('renders', async () => {
const scope = nock(TEST_GRAPHQL_URL)
.post(TEST_GRAPHQL_PATH_MATCHER)
.reply(200, {
data: {
viewer: {
id: 'test-viewer-id',
userProperties: {
id: 'test-user-properties-id',
name: 'test-user-name',
},
},
},
});

const environment = await createTestEnvironment({});


const { findByTestId } = render(<TestFixture environment={environment} />);
expect(await findByTestId('user-properties')).toBeVisible();
scope.done();
});
const { viewer } = useLazyLoadQuery<UserPropertiesQuery>(
graphql`
query UserPropertiesQuery {
viewer {
userProperties {
name
regionFeatures {
isInUs
}
}
}
}
`,
{},
);
const { viewer } = useLazyLoadQuery<UserPropertiesQuery>(
graphql`
query UserPropertiesQuery {
viewer {
userProperties {
name
regionFeatures {
isInUs
}
}
}
}
`,
{},
);
it('renders', async () => {
const scope = nock(TEST_GRAPHQL_URL)
.post(TEST_GRAPHQL_PATH_MATCHER)
.reply(200, {
data: {
viewer: {
id: 'test-viewer-id',
userProperties: {
id: 'test-user-properties-id',
name: 'test-user-name',
},
regionFeatures: {
isInUs: true,
},
},
},
});
const environment = await createTestEnvironment({});
const { findByTestId } = render(<TestFixture environment={environment} />);
expect(await findByTestId('user-properties')).toBeVisible();
scope.done();
});
import { graphql } from 'msw';

export function createHandlers(): (


| RestHandler<MockedRequest<DefaultBodyType>>
| GraphQLHandler<GraphQLRequest<GraphQLVariables>>
)[] {
return [
graphql.query('UserPropertiesQuery', (req, res, ctx) => {
let testName = 'test-user-name';

if (req.variables?.id === 2) {
testName = 'test-user-name-2';
}

return res(
ctx.data({
viewer: {
id: 'test-viewer-id',
userProperties: {
id: 'test-user-properties-id',
name: testName,
},
const server = createTestMockServiceWorker(...);

beforeAll(() => server.listen());

afterEach(() => server.resetHandlers());

afterAll(() => server.close());


import { MockedProvider } from "@apollo/client/testing";

render(
<MockedProvider mocks={mocks} addTypename={false}>
<UserProperties />
</MockedProvider>
);
const mocks = [
{
request: {
query: UserPropertiesQuery,
variables: {
id: 42
}
},
result: {
data: {
viewer: {
id: 'test-viewer-id',
userProperties: {
id: 'test-user-properties-id',
name: 'test-user-name',
},
},
},
}
}
];
relayEnvironment.mock.queueOperationResolver((operation) => {
return MockPayloadGenerator.generate(operation);
});
const { viewer } = useLazyLoadQuery<UserPropertiesQuery>(
graphql`
query UserPropertiesQuery {
viewer {
userProperties {
name
regionFeatures {
isInUs
}
}
}
}
`,
{},
);
const { viewer } = useLazyLoadQuery<UserPropertiesQuery>(
graphql`
query UserPropertiesQuery {
viewer {
userProperties {
name
regionFeatures {
isInUs
}
createdAt
}
}
}
`,
{},
);

const date = formatDate(viewer.userProperties.createdAt);


relayEnvironment.mock.queueOperationResolver((operation) => {
return MockPayloadGenerator.generate(operation, {
UserProperties() {
return {
createdAt: '2021-09-01T00:00:00.000Z',
};
},
});
});
const otherData = useGetOtherData(); // hook with its own query

const { viewer } = useLazyLoadQuery<UserPropertiesQuery>(


graphql`
query UserPropertiesQuery {
viewer {
userProperties {
name
regionFeatures {
isInUs
}
createdAt
}
}
}
`,
{},
);

const date = formatDate(viewer.userProperties.createdAt);


relayEnvironment.mock.queueOperationResolver((operation) => {
return MockPayloadGenerator.generate(operation, {
UserProperties() {
return {
createdAt: '2021-09-01T00:00:00.000Z',
};
},
});
});

relayEnvironment.mock.queueOperationResolver((operation) => {
return MockPayloadGenerator.generate(operation);
});
● Recap - Common Issues
○ Nock
■ Static mocks
■ No easy access to query variables
○ MSW
■ Static mocks
■ No React Native support
■ Extra config
○ Apollo
■ Static mocks (even variables)
○ Relay
■ Dynamic mocks only return strings
■ Have to mock operations in order
What we need:
● No mocking required just to render
○ Adding queries/fields to a component does
not break everything
● Clear access to query variables
○ And all request data
● Test should be easy to set up, with very little code
import { faker } from '@faker-js/faker';

export const defaultMocks = {


// scalars
String: () => 'mock-value-for-string',
Boolean: () => true,
Int: () => 1,
Float: () => '2.42',

// custom scalars
Uuid: () => faker.datatype.uuid(),
Email: () => '[email protected]',
Date: () => '2022-11-12',
Time: () => '2022-11-12T16:06:15.442Z',
Decimal: () => '42.42',
Url: () => 'http://testUrl.com',
TickerSymbol: () => 'USD',
CountryCode: () => 'US',
Json: () => {},
Locale: () => 'en',
PhoneNumber: () => '4152564879',
return createGraphqlEnvironment({
. . .
interceptors: {
queryLevelMockerConfig: {
queryLevelMockerMiddleware: createQueryLevelMockerMiddleware({
schema,
mockEnums,
mockOverrides,
mockResolvers,
baseMocks,
debugOperations,
}),
},
},
});
it('renders', async () => {

const environment = createMockedGraphQLEnvironment();

const { findByTestId } = render(<TestFixture environment={environment} />);

expect(await findByTestId('user-properties')).toBeVisible();

});
return createGraphqlEnvironment({
. . .
interceptors: {
queryLevelMockerConfig: {
queryLevelMockerMiddleware: createQueryLevelMockerMiddleware({
schema,
mockEnums,
mockOverrides,
mockResolvers,
baseMocks,
debugOperations,
}),
},
},
});
const fieldLevelMockData = [
{
fieldOverride: [
{
path: 'viewer.userProperties.email',
value: '[email protected]',
},
],
},
];

const environment = createGraphqlEnvironment({


. . .
interceptors: {
fieldLevelMockerConfig: {
mockData: fieldLevelMockData,
},
},
});
fieldOverride: [
{
path: 'viewer.userProperties.email',
value: null, // simulates a resolver error
},
{
path: 'updateUserAttribute', // mutation name
value: {
__typename: 'GenericError', // indicates union type
message: 'An error has occurred',
variables: {
// can be omitted to allow a match to any inputs
updateUserAttribute: {
input: {
value: 'John Doe',
attribute: 'name',
},
},
},
},
},
],
# clientSchema.graphql
extend type UserProperties {
city: String
}

const fieldLevelMockData = [
{
fieldOverride: [
{
path: 'viewer.userProperties.city',
value: 'Denver',
},
],
},
];
Development
Sandbox
Development
Sandbox
Benefits of Sophisticated Schema Mocking:
● Automatically mock queries Thank You!

○ Easy to write and maintain unit tests


○ Full code coverage by default
● Mocking individual fields
○ Reproduce incidents
○ Easy test specific scenarios
● Unlocks possibilities for other great tools
Improving User Experiences with
a Nullable Schema
Ernie Turner, Coinbase

#GraphQLConf

You might also like