How to test your relay components with relay-test-utils and react-testing-library

augustocalaca

Augusto Calaca

Posted on February 20, 2020

How to test your relay components with relay-test-utils and react-testing-library

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();
});

Enter fullscreen mode Exit fullscreen mode

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();
});

Enter fullscreen mode Exit fullscreen mode

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();
  });

Enter fullscreen mode Exit fullscreen mode

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
  });

Enter fullscreen mode Exit fullscreen mode

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.

💖 💪 🙅 🚩
augustocalaca
Augusto Calaca

Posted on February 20, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related