Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Testing a Relay Modern GraphQL app part 1: Testing a simple query #1

Open
zth opened this issue Jan 30, 2020 · 0 comments
Open

Testing a Relay Modern GraphQL app part 1: Testing a simple query #1

zth opened this issue Jan 30, 2020 · 0 comments
Assignees
Labels
Publish Add this label to an issue in order for it to be published on the blog

Comments

@zth
Copy link
Owner

zth commented Jan 30, 2020

GraphQL + Relay is one of those things that simply changed my way of thinking about UIs and data. Building components with clear boundaries and explicit data requirements just clicked for me. In this article series I’ll try and outline my approach to testing Relay Modern apps in an as realistic way as possible.

I’ll also make use of Flow. Using static typing like Flow or TypeScript handles tons of things you’d otherwise need to cover with tests, and I strongly recommend using something like it if you can.

All articles in this series will be linked here as soon as they’re published:

  1. Setup and testing a simple query (this one)
  2. Testing pagination, refetch containers, errors and loading states (coming soon)
  3. Testing mutations, optimistic updates and local updates (coming soon)
  4. Wrap up and tips and tricks (coming soon)

I’ve made a sample repository that will show all of the techniques I outline in this series of articles. It’ll be updated as each article is written, and you can check it out here:

https://github.com/zth/relay-modern-flow-jest-example

Useful UI testing

So, there’s tons of different types of tests you could write for your typical UI. Tests of single components, styling being added and removed, the right methods being called. But, chances are if your ambitions are too high and if your tests are too detailed, you’ll write less and less of them. And that’s a way bigger problem than not testing enough detail.

So, what can we do to avoid that? Well, we want to make sure that whatever testing we do is as efficient and realistic as possible. We want to cover as much ground as we can with as little effort as possible. Contrary to other testing methods, when testing UI on a high level, you’ll want to use broad strokes and test as much as you can of your app in one go. Forget the mantra of testing things in isolation, and focus writing tests in a way that a user would actually use your app.

This philosophy means a few things for us:

  • We want to test actual output like rendered text and buttons, since that’s what the user would interact with
  • We want to come as close as possible to how the code would run in production — this means we want to simulate actual requests where possible, and mock with as real data as we can get
  • We want to avoid testing implementation details. What a method is called or what exact props a component takes is not interesting in a UI test; only the output is

Setup

For this to work efficiently, we have a few requirements:

  • We need to use a library that allows us to render to a DOM and interact with the results in a way that’s as close to how a user would as possible
  • We need to have a good way of obtaining real data and then use that data as mock data in our tests

Luckily, there are solutions for both. For rendering our app and interacting with it, we’ll use react-testing-library. It’s a great lib that forces us to write tests that aren’t tied to implementation details.

For mocking our data fetching with Relay Modern we’ll use graphql-query-test-mock, which is a lib I’ve written to ease both simple and complex mocking scenarios for testing apps using GraphQL clients like Relay Modern and Apollo.


Adding the libraries

We’ll start by installing the dependencies we need. Nock is a mocking library needed by graphql-query-test-mock, so we’ll install that. We also need to make sure there’s a fetch implementation available globally as Jest don’t provide one of its own. We’ll add node-fetch to fix that.

yarn add react-testing-library graphql-query-test-mock nock node-fetch --dev

Setting up the libraries in your tests

Both react-testing-library and graphql-query-test-mock requires a little bit of setup in order to be properly cleaned up and reset between tests. We’ll make use of Jests setupTestFramework and setupFiles configuration options. If you’re not familiar with those two configurations in Jest, go ahead and look them up before continuing.

First, we’ll create and export a QueryMock using graphql-query-test-mock that we’ll use in all our tests to mock our queries. Go ahead and create a file somewhere accessible, and paste the following.

// @flow
import { QueryMock } from 'graphql-query-test-mock';

export const queryMock = new QueryMock();

A quick note: graphql-query-test-mock needs a way of figuring out what query or mutation is dispatched through the network layer, so if you haven’t already for other reasons, make sure you pass the query name when making requests. See an example passing name from operation in the body in fetchQuery below:

https://github.com/zth/relay-modern-flow-jest-example/blob/master/src/config/fetchQuery.js

In the file you’re using for setupTestFramework, make sure you have the following (this will run before each test):

import { cleanup } from 'react-testing-library';
import { queryMock } from '../path/to/your/queryMock';
beforeEach(() => {
  // Make sure no mocks stick around between tests
  queryMock.reset();
}); 
// Clean up any mounted DOM by react-testing-library
afterEach(cleanup);

And finally, in a file you use as setupFile with Jest, make sure you have this:

import { queryMock } from '../path/to/your/queryMock';
/**
* Initialize our queryMock and pass in the URL you use to make requests to your GraphQL API. */
queryMock.setup(GRAPHQL_API_URL);
/**
 * Jest has no fetch implementation by default. We make sure fetch exists in our tests by using node-fetch here. */
global.fetch = require('node-fetch');

There, all set up!


Testing your first query

Ok, lots of introduction. Time for the fun part! Let’s write our first test. This test will be really simple; we’ll mount our app, let it make it’s initial query, and make sure it renders.

Here’s the sample component we’ll use. Note that we’re making use of Flow and Relay’s auto generated Flow definitions for queries. This is awesome and in my opinion one of the primary features of Relay Modern.

// @flow
import * as React from 'react';
import { QueryRenderer, graphql } from 'react-relay';
import { environment } from '../config/createRelayEnvironment';
import type { AppQueryResponse } from './__generated__/AppQuery.graphql';

const query = graphql`
  query AppQuery {
    viewer {
      id
      currentUserCount
    }
  }
`;

export class App extends React.Component<{}> {
  render() {
    return (
      <div className="App">
        <QueryRenderer
          environment={environment}
          query={query}
          render={({
            error,
            props
          }: {
            error: Error,
            props: AppQueryResponse
          }) => {
            if (error) {
              console.error(error);
              return <div className="ErrorScreen">Something went wrong!</div>;
            }

            if (props) {
              return (
                <div className="AppDisplayer">
                  <h1>User count: {props.viewer.currentUserCount}</h1>
                </div>
              );
            }

            // No error or props means we're loading still
            return <div className="AppDisplayer--loading">Loading app...</div>;
          }}
        />
      </div>
    );
  }
}

So, this component makes a query called AppQuery which fetches a user count that we then show in a title. Let’s go ahead and make a very simple test:

// @flow
import * as React from 'react';
import { queryMock } from '../../__testUtils__/queryMock'; // Or wherever your queryMock is located
import { App } from '../App';
import { render, wait } from 'react-testing-library';

describe('App', () => {
  it('should render the current user count', async () => {
    /**
     * App fetches the query AppQuery. Here, we mock all requests for that query.
     */

    queryMock.mockQuery({
      name: 'AppQuery',
      data: {
        viewer: {
          id: '1',
          currentUserCount: 12
        }
      }
    });

    /**
     * We mount the app and wait for our element that displays the app's content
     * to be visible.
     */

    const r = render(<App />);

    // If App works, we'll see "User count: 12" when it has made its request and rendered.
    await wait(() => r.getByText('User count: 12'));

    // There! That's all that's needed. If "User count: 12" does not appear within the timeout, something's up and the test will fail.
  });
});

There! With this small and simple test we’ve actually tested our whole setup. This test passing actually tells us that our Relay setup is working all the way from query -> network -> render.

… And that’s it! Next article in this series is about more complex testing of queries, testing pagination and testing states in your app like errors and loading. Thanks for reading!


A final remark

  • We didn’t cover it here, but remember to make sure you always reset your Relay environment between tests. If you don’t, the store might contain data from prior tests, making tests that shouldn’t pass pass, and tests that shouldn’t fail fail. Resetting the environment means creating a new one. There are a few techniques you can use for this, let me know if you want this covered in a future article and I’ll make sure to add it.
{"source":"medium","postId":"559b6d7fe9f8","publishedDate":1535490679736,"url":"https://medium.com/@_zth/testing-a-relay-modern-graphql-app-part-1-testing-a-simple-query-559b6d7fe9f8"}
@zth zth added the Publish Add this label to an issue in order for it to be published on the blog label Jan 30, 2020
@zth zth self-assigned this Jan 30, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Publish Add this label to an issue in order for it to be published on the blog
Projects
None yet
Development

No branches or pull requests

1 participant