This blogpost is an improvement over a thread I did on twitter some time ago.
Now I show a similar example but with the use of relay hooks and a little more information.
If you need to refactor code and have no tests to offer you coverage and security, unfortunately, you will be in trouble. Tests really improves the stability of the codebase and helps with changing the internal implementation of the components.
Relay-test-utils
Testing applications that are using relay may be challenging, but the relay-test-utils makes things a lot easier.
It provides imperative APIs for controlling the request/response flow and additional API for mock data generation.
There are two main modules that you will enjoy using in your tests:
- createMockEnvironment
- mockPayloadGenerator
The createMockEnvironment
nothing more than an implementation the Relay Environment Interface and it also has an additional mock layer, with methods that allow resolving/reject and control of operations (queries/mutations/subscriptions).
The mockPayloadGenerator
is to improve the process of creating and maintaining the mock data for tested components
it can generate dummy data for the selection that you have in your operation.
Consider that we want to test if this transaction listing goes as expected:
The code above use the hooks useLazyLoadQuery
and usePaginationFragment
to load a transactions list in which each has the user who is sending fromUser
, the user who is receiving toUser
, the value
, the cashback
(5% of the value) and any message
.
RelayMockEnvironment
CreateMockEnvironment
is a special version of Relay Environment with an additional API methods for controlling the resolving and rejection operations. Therefore the first thing we should do is tell jest that we don't want the default environment but our environment provided by relay-test-utils
.
jest.mock('path/to/Environment', () => {
const { createMockEnvironment } = require('relay-test-utils');
return createMockEnvironment();
});
The relay team is very concerned with reducing the preparation time of the test environment so that the developer can focus on actually testing its components. That way, from there we can already test our component.
With React-testing-library
Reject
Here we use object destructuring to capture the value of getByText
from our render()
function.
Let's use rejectMostRecentOperation
first and we check if the component raises the error or not. In the first tests, I always like to test cases where the component may fail.
import React from 'react';
import { render, cleanup } from '@testing-library/react';
import { MockPayloadGenerator } from 'relay-test-utils';
import { useRelayEnvironment } from 'react-relay/hooks';
import TransactionList from '../TransactionList';
afterEach(cleanup);
it('should reject query', () => {
const Environment = useRelayEnvironment();
const { getByText } = render(<TransactionList />);
Environment.mock.rejectMostRecentOperation(new Error('A very bad error'));
expect(getByText('Error: A very bad error')).toBeTruthy();
});
We can also check if the error occurs in the expected operation.
it('should reject query with function and render error with name of the operation', () => {
const Environment = useRelayEnvironment();
const { getByText } = render(<TransactionList />);
Environment.mock.rejectMostRecentOperation((operation) =>
new Error(`A error occurred on operation: ${operation.fragment.node.name}`)
);
expect(getByText('Error: A error occurred on operation: TransactionListQuery')).toBeTruthy();
});
Resolve
To use resolveMostRecentOperation
is as simple as reject.
We check if the component show loading transactions...
on the screen while waiting by data and resolve a query.
Finally, we solve the query by putting our mock resolver to the generate.
it('should render success TransactionList', async () => {
const Environment = useRelayEnvironment();
const { getByText } = render(<TransactionList />);
expect(getByText('loading transactions...')).toBeTruthy();
Environment.mock.resolveMostRecentOperation(operation =>
MockPayloadGenerator.generate(operation, {
PageInfo() {
return {
hasNextPage: false,
hasPreviousPage: false,
startCursor: "YXJyYXljb25uZWN0aW9uOjA=",
endCursor: "YXJyYXljb25uZWN0aW9uOjE="
}
},
TransactionEdge() {
return [
{
cursor: "YXJyYXljb25uZWN0aW9uOjA=",
node: {
id: "Q2xpZW50OjE=",
fromUser {
user: "George Lima",
username: "georgelima",
},
toUser {
user: "Augusto Calaca",
username: "augustocalaca",
},
value: 1000,
cashback: 50,
message: 'A gift on your birthday'
}
},
{
cursor: "YXJyYXljb25uZWN0aW9uOjE=",
node: {
id: "Q2xpZW50OjI=",
fromUser {
user: "Bori Silva",
username: "bori",
},
toUser {
user: "Augusto Calaca",
username: "augustocalaca",
},
value: 500,
cashback: 25,
message: 'This transaction yielded cashback'
}
}
]
}
})
);
expect(getByText('FromUser')).toBeTruthy();
expect(getByText('Name: George Lima')).toBeTruthy();
expect(getByText('Username: georgelima')).toBeTruthy();
expect(getByText('ToUser')).toBeTruthy();
expect(getByText('Name: Augusto Calaca')).toBeTruthy();
expect(getByText('Username: augustocalaca')).toBeTruthy();
expect(getByText('Value: 1000')).toBeTruthy();
expect(getByText('Cashback: 50')).toBeTruthy();
expect(getByText('Message: A gift on your birthday')).toBeTruthy();
expect(getByText('FromUser')).toBeTruthy();
expect(getByText('Name: Bori Silva')).toBeTruthy();
expect(getByText('Username: bori')).toBeTruthy();
expect(getByText('ToUser')).toBeTruthy();
expect(getByText('Name: Augusto Calaca')).toBeTruthy();
expect(getByText('Username: augustocalaca')).toBeTruthy();
expect(getByText(/Value: 500/)).toBeTruthy();
expect(getByText(/Cashback: 25/)).toBeTruthy();
expect(getByText(/Message: This transaction yielded cashback/)).toBeTruthy(); // this is a default message
});
Conclusion
This is probably the baseline rule to follow when it comes to testing your relay components.
You can follow the whole the code used above on post through this link.
Top comments (5)
You can just import the mocked environment in your tests and use it there
How are you able to use the
useRelayEnvironment
hook in your tests? Do you not get an invalid hook call error? How does the<TransactionList />
know what environment to use?yea I get the same error, an answer would be nice.
I use
createMockEnvironment
fromreact-test-utils
. Then I create a barebone component in my tests using QueryRenderer that wraps the component that I am testing, passing the fragment reference down. It's a bit of boilerplate code.Doesn't take props?