How to test your relay components with relay-test-utils and react-testing-library
Tags: tutorial, graphql, testing, webdev
Published: 2020-02-20 22:04:20


This blogpost is an improvement over a thread I did on twitter some time ago.

{% twitter 1191733748481613825 %}

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](https://relay.dev/docs/en/testing-relay-components) 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:

{% gist https://gist.github.com/AugustoCalaca/057b501d9f3c4be64c629089467bacc7 %}

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`.

```js

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.

```jsx
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.

```jsx

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.

```jsx

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](https://gist.github.com/AugustoCalaca/f4913a09a7e3f265513f6cc60ea5a3b6).