website/docs/guides/testing-relay-with-preloaded-queries.mdx
import DocsRating from '@site/src/core/DocsRating'; import {FbInternalOnly, OssOnly} from 'docusaurus-plugin-internaldocs-fb/internal';
Components that use preloaded queries (useQueryLoader and usePreloadedQuery hooks) require slightly different and more convoluted test setup.
In short, there are two steps that need to be performed before rendering the component
environment.mock.queueOperationResolverenvironment.mock.queuePendingOperationSuspend doesn't switch from "waiting" to "data loaded" stateconsole.log before and after usePreloadedQuery, only the "before" call is hitconst {RelayEnvironmentProvider} = require('react-relay');
const {MockPayloadGenerator, createMockEnvironment} = require('relay-test-utils');
const {act, render} = require('@testing-library/react');
test("...", () => {
// arrange
const environment = createMockEnvironment();
environment.mock.queueOperationResolver(operation => {
return MockPayloadGenerator.generate(operation, {
CurrencyAmount() {
return {
formatted_amount: "1234$",
};
},
});
});
const query = YourComponentGraphQLQueryGoesHere; // can be the same, or just identical
const variables = {
// ACTUAL variables for the invocation goes here
};
environment.mock.queuePendingOperation(YourComponentGraphQLQuery, variables);
// act
const {getByTestId, ..otherStuffYouMightNeed} = render(
<RelayEnvironmentProvider environment={environment}>
<YourComponent data-testid="1234" {...componentPropsIfAny}/>
</RelayEnvironmentProvider>
);
// trigger the loading - click a button, emit an event, etc. or ...
act(() => jest.runAllImmediates()); // ... if loadQuery is in the useEffect()
// assert
// your assertions go here
});
This is done via environment.mock.queueOperationResolver(operation) call, but getting it right might be tricky.
The crux of this call is to return a mocked graphql result in a very particular format (as MockResolvers type, to be precise). This is done via a second parameter to generate - it is an object, whose keys are GraphQL types that we want to mock. (See mock-payload-generator).
Continuing on the above example:
return MockPayloadGenerator.generate(operation, {
CurrencyAmount() { // <-- the GraphQL type
return {
formatted_amount: "response_value" <-- CurrencyAmount fields, selected in the query
};
}
});
The tricky thing here is to obtain the name of the GraphQL type and fields to return. This can be done in two ways:
console.log(JSON.stringify(operation, null, 2)) and look for the concreteType that corresponds to what we want to mock. Then look at the sibling selections array, which describes the fields that are selected from that object.:::note The type you need seems to be the type returned by the innermost function call (or calls, if you have multiple functions called in one query - see D23078476). This needs to be confirmed - in both example diffs the target types was also leafs. :::
</FbInternalOnly>It is possible to return different data for different query variables via Mock Resolver Context. The query variables will be available on the context.args, but only to the innermost function call (for the query above, only offer_ids are available)
CurrencyAmount(context) {
console.log(JSON.stringify(context, null, 2)); // <--
return { formatted_amount: mockResponse }
}
// <-- logs { ...snip..., "name": "subtotal_price_for_offers", args: { offer_ids: [...] } }
This is more straightforward - it is done via a call to environment.mock.queuePendingOperation(query, variables)
Query needs to match the query issues by the component. Simplest (and most robust against query changes) is to export the query from the component module and use it in the test, but having an identical (but not the same) query works as well.variables has to match the variables that will be used in this test invocation.
areEqual (invocation code)
console.log, console.log everywhere! Recommended places:
useQueryLoader, usePreloadedQuery, loadQueryqueueOperationResolver callbackRelayModernMockEnvironment.execute, after the const currentOperation = ... call (here)loadQuery is not called - make sure to issue the triggering event. Depending on your component implementation it could be a user-action (like button click or key press), javascript event (via event emitter mechanisms) or a simple "delayed execution" with useEffect.
useEffect case is probably easiest to miss - make sure to call act(() => jest.runAllImmediates()) after rendering the componentusePreloadedQuery is hit, but "after" is not - the query suspends. This entire guide is written to resolve it - you might want to re-read it. But most likely it is either:
currentOperation will be nullcurrentOperation will be null (make sure to inspect the variables).
<mock-value-for-field-"formatted_amount">:::note
Make sure the component and the test use the same environment (i.e. there's no <RelayEnvironmentProvider environment={RelayFBEnvironment}> somewhere nested in your test React tree.
:::
Examples here use testing-library-react, but it works with the react-test-renderer as well.
See D23078476.
</FbInternalOnly> <DocsRating />