Git Product home page Git Product logo

oneblog's People

Contributors

zth avatar

Watchers

 avatar  avatar

oneblog's Issues

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

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"}

Hello from Stockholm

The word "inlägg" means "post", "posts" and "shoe insole".

👍 👎 💯 🥇

Screen Shot 2017-06-05 at 10 14 27

Another section, now in swedish

Hej på er. Svenska är ett fint språk som alla borde kunna.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.