You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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:
Setup and testing a simple query (this one)
Testing pagination, refetch containers, errors and loading states (coming soon)
Testing mutations, optimistic updates and local updates (coming soon)
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:
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.
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:
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.
// @flowimport*asReactfrom'react';import{QueryRenderer,graphql}from'react-relay';import{environment}from'../config/createRelayEnvironment';importtype{AppQueryResponse}from'./__generated__/AppQuery.graphql';constquery=graphql` query AppQuery { viewer { id currentUserCount } }`;exportclassAppextendsReact.Component<{}>{render(){return(<divclassName="App"><QueryRendererenvironment={environment}query={query}render={({
error,
props
}: {error: Error,props: AppQueryResponse})=>{if(error){console.error(error);return<divclassName="ErrorScreen">Something went wrong!</div>;}if(props){return(<divclassName="AppDisplayer"><h1>User count: {props.viewer.currentUserCount}</h1></div>);}// No error or props means we're loading stillreturn<divclassName="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:
// @flowimport*asReactfrom'react';import{queryMock}from'../../__testUtils__/queryMock';// Or wherever your queryMock is locatedimport{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. */constr=render(<App/>);// If App works, we'll see "User count: 12" when it has made its request and rendered.awaitwait(()=>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.
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:
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:
Setup
For this to work efficiently, we have a few requirements:
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.
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.
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):
And finally, in a file you use as setupFile with Jest, make sure you have this:
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.
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:
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
The text was updated successfully, but these errors were encountered: