What to test in the frontend? was a success.
In this article, I will show some practical examples of how to test Frontend when using React and Relay.
Asserting and mocking GraphQL Requests using Relay
When testing a React app that uses Relay, we need to mock GraphQL requests that would be sent to our GraphQL server. We don't want to reach the real server, as we are doing integration tests, and e2e is very slow.
We prefer to reduce the scope of our test, and assume the GraphQL server will behave correctly, so we can focus on test frontend behavior.
Let's test this App
component
const App = () => {
const response = useLazyLoadQuery<AppQuery>(
graphql`
query AppQuery {
me {
name
}
}
`,
{},
{},
);
return (
<span>{response.me.name}</span>
)
}
This test will mock the AppQuery
GraphQL request, it will await the request to be resolved by Relay, and it will finally assert the DOM has the value returned in the me { name }
field.
Firstly, we need to create a mock environment, that will let us mock GraphQL requests easily.
import { createMockEnvironment } from 'relay-test-utils';
const environment = createMockEnvironment();
We then render our component using @testing/library
import { render } from '@testing-library/react';
render(<App />);
Before asserting anything in the DOM, we need to assert and mock the AppQuery
request
We can assert that App
component made the AppQuery
request like this
assertOperation(environment, 'AppQuery', {});
We can resolve/mock the request like this:
const mockResolvers = {
User: () => ({ name: 'Woovi' }),
};
resolveMostRecentOperation(environment, mockResolvers);
We then await the loading View to be removed from the DOM, so we know the request was resolved
await waitLoading();
We validate that there are no more pending requests to be mocked
assertNoPendingOperations(environment);
And we finally assert the DOM has the value that was mocked in our request
expect(screen.getByText('Woovi')).toBeTruthy();
Relay test helpers
The above code uses some helpers to make testing Relay more declarative, lets show how they are implemented here:
assertOperation - assert operation name and variables
type OperationType = {
variables: Record<string, unknown>;
};
export const assertOperation = <T extends OperationType>(
environment: RelayMockEnvironment,
name: string,
variables: T['variables'] = {},
) => {
const queryOperation = environment.mock.getMostRecentOperation();
const operationName = getOperationName(queryOperation);
const operationVariables = getOperationVariables(queryOperation);
expect(operationName).toBe(name);
expect(operationVariables).toEqual(variables);
};
resolveMostRecentOperation - mock the most recent operation
export const resolveMostRecentOperation = (
environment: RelayMockEnvironment,
customMockResolvers: MockResolvers,
) => {
act(() => {
environment.mock.resolveMostRecentOperation((operation) => {
return MockPayloadGenerator.generate(operation, customMockResolvers);
});
});
};
waitLoading - await loading element to be removed
export const waitLoading = async () => {
await waitForElementToBeRemoved(screen.queryByTestId('loading'), {
timeout: 20000,
});
}
In Conclusion
Testing Frontend with React and Relay can add some complexity to your tests, but if you create declarative helpers it can be very intuitive and easy.
Woovi
Woovi is a Startup that enables shoppers to pay as they like. To make this possible, Woovi provides instant payment solutions for merchants to accept orders.
If you want to work with us, we are hiring!
Photo by Roman Mager on Unsplash
Top comments (4)
Have you guys ever used the queueOperationResolver to treat the mocked queries instead of having to call the resolveMostRecentOperation every time?
it could work as well
Yeah, I was trying it out but my expectation was that the queueOperationResolver would trigger the mocking resolver for any query that would be added to the queue, but for me it only works for the first query. So for tests which depend on more than one query, it always times out on the second. And the docs only have examples with a single query. I even added a stack overflow question stackoverflow.com/q/79200010/7196423 but no luck so far.
I think queueOperationResolver is more about preload queries