10

I'm using react-apollo to build a client that consumes a GraphQL API, however, I'm very stuck on testing. What I want is to mock the server so I can easily test the application without needing to make network calls.

I've found some pointers on how to mock the server:

But there isn't really an example on how to use this mocked server in my app tests to avoid hitting the server.

My goal is to setup integration tests to assert that the app is actually working:

describe('Profile feature', () => {
  beforeAll(() => {
    store = setupStore();
    app = mount(
      <ApolloProvider store={store} client={apolloClient}>
        <ConnectedRouter history={history}>
          <App />
        </ConnectedRouter>
      </ApolloProvider>
    );
  });
});

The store is using Redux and the client is being created like this:

const networkInterface = createNetworkInterface({
  uri: process.env.REACT_APP_API_URL
});

export const apolloClient = new ApolloClient({
  networkInterface
});

How can I use a mocked server with graphql-tools here instead of the actual API?

Leo Jiang
  • 24,497
  • 49
  • 154
  • 284
Carlos Martinez
  • 4,350
  • 5
  • 32
  • 62

3 Answers3

13

I found 2 different ways of creating mocked data for apollo-client queries:

The first is to use graphql-tools to create a mocked server based on your backend schema, in order to connect this mocked server with your tests it's possible to create a mockNetworkInterface like this:

const { mockServer } = require("graphql-tools");
const { print } = require("graphql/language/printer");


class MockNetworkInterface {
  constructor(schema, mocks = {}) {
    if (schema === undefined) {
      throw new Error('Cannot create Mock Api without specifying a schema');
    }
    this.mockServer = mockServer(schema, mocks);
  }

  query(request) {
    return this.mockServer.query(print(request.query), request.variables);
  }
}

You can pass this network interface to the ApolloClient component and it should work just fine!

Having this setup requires to have your API schema up to date in your client, so I found it a bit of a pain to do.

Another way of doing this is using the mockNetworkInterface provided by apollo-client/test-utils

You can use it this way:

import App from './App';
import { UserMock, PublicationMock } from '../__mocks__/data';
import { mockNetworkInterface } from 'react-apollo/test-utils';
import ApolloClient from 'apollo-client';
import { ApolloProvider } from 'react-apollo';

// We will be using here the exact same Query defined in our components
// We will provide a custom result or a custom error
const GraphQLMocks = [
  {
    request: {
      query: UserProfileQuery,
      variables: {}
    },
    result: {
      data: {
        current_user: UserMock
      }
    }
  }
];

// To set it up we pass the mocks to the mockNetworkInterface
const setupTests = () => {
  const networkInterface = mockNetworkInterface.apply(null, GraphQLMocks);
  const client = new ApolloClient({ networkInterface, addTypename: false });

  const wrapper = mount(
    <ApolloProvider client={client}>
      <App />
    </ApolloProvider>
  );

  return {
    store,
    wrapper
  };
};

// Then the tests look like this
describe('Profile feature', () => {
  test('Profile view should render User details', async () => {
    const { wrapper, store } = setupTests();

    const waitFor = createWaitForElement('.profile');

    await waitFor(wrapper);

    const tag = wrapper.find('.profile-username');
    expect(tag.text()).toEqual(`${UserMock.first_name} ${UserMock.last_name}`);
  });
});

It is important to pass addTypename: false to the ApolloClient instance, otherwise you will need to add __typename to all your queries manually.

You can inspect the implementation of the mockNetworkInterface here: https://github.com/apollographql/apollo-test-utils/blob/master/src/mocks/mockNetworkInterface.ts

BrightIntelDusk
  • 4,577
  • 2
  • 25
  • 34
Carlos Martinez
  • 4,350
  • 5
  • 32
  • 62
  • 1
    Thank you for your question and answer! This put me on the right path after hours of searching! – Deividas Aug 23 '17 at 17:55
  • Glad to help, I struggled with this for a while too – Carlos Martinez Aug 23 '17 at 18:53
  • @CarlosMartinez which one is the generally accepted best practice or the best supported library/option? – arcom Sep 08 '17 at 01:52
  • The most supported option would be using graphql-tools, but they need an easier integration for testing. I found using mockNetworkInterface to be much easier to work with. – Carlos Martinez Sep 09 '17 at 00:05
  • 1
    @CarlosMartinez this was great! I know how to test the server side code no problem but was lost about how to manage the front end code base with the `react-apollo` integration. Fantastic write up, and thanks so much for supplying your research on this matter. – rockchalkwushock Sep 11 '17 at 11:59
  • 1
    I'm getting the error `TypeError: Cannot read property 'apply' of undefined` when attempting to run this code. – BrightIntelDusk Jun 29 '18 at 14:44
7

You can also use MockedProvider, which makes it even simpler.

withPersons.js

import { gql, graphql } from 'react-apollo'

export const PERSONS_QUERY = gql`
  query personsQuery {
    persons {
      name
      city
    }
  }
`

export const withPersons = graphql(PERSONS_QUERY)

withPersons.test.js

/* eslint-disable react/prop-types */

import React, { Component } from 'react'
import { MockedProvider } from 'react-apollo/test-utils'

import { withPersons, PERSONS_QUERY } from '../withPersons'

it('withPersons', (done) => {
  const mockedData = {
    persons: [
      {
        name: 'John',
        city: 'Liverpool',
      },
      {
        name: 'Frank',
        city: 'San Diego',
      },
    ],
  }

  const variables = { cache: false }

  class Dummy extends Component {
    componentDidMount() {
      const { loading, persons } = this.props.data
      expect(loading).toBe(true)
      expect(persons).toBe(undefined)
    }

    componentWillReceiveProps(nextProps) {
      const { loading, persons } = nextProps.data

      expect(loading).toBe(false)
      expect(persons).toEqual(mockedData.persons)
      done()
    }

    render() {
      return null
    }
  }
  const DummyWithPersons = withPersons(Dummy)
  mount(
    <MockedProvider
      removeTypename
      mocks={[
        {
          request: { query: PERSONS_QUERY, variables },
          result: { data: mockedData } },
      ]}
    >
      <DummyWithPersons />
    </MockedProvider>,
  )
})

Note: By using a Dummy component you just test your graphql() Queries and Mutations and the way you have configured them (options, props, skip, variables, etc.) So you don't mount your actual React components. It's better to test those in their 'unconnected' state.

devboell
  • 1,180
  • 2
  • 16
  • 34
  • Yes you can, my only problem with MockedProvider was that you couldn't pass addTypename: false to the underlying apollo client, so your mocks would need to include __typename everywhere. They merged a PR I opened to fix this https://github.com/apollographql/react-apollo/pull/1001 – Carlos Martinez Sep 14 '17 at 16:05
  • Oh I see, that's your prop! haha - I only noticed it today – devboell Sep 14 '17 at 18:13
  • How would you do it when using Render Props instead of HOCs? – Robin Wieruch Jun 04 '18 at 02:32
  • I am not using this approach anymore, I switched to integration testing for my components. Also, I am not using the render prop components yet, so I couldn't tell you, sorry. – devboell Jun 05 '18 at 06:09
  • @devboell how's your integration test strategy? could you share an overview of how are you testing it this way, as I believe it might be the path I want to follow as well. thanks – Daniel Ocampo Aug 11 '18 at 11:12
  • 1
    @DanielOcampo I don't have time to do a write up, but I made a gist with some code snippets from a project where I use this approach. https://gist.github.com/devboell/971c5477532fcae674c49110ab720ceb – devboell Aug 12 '18 at 08:57
1

I wrote up a blog post a while that might be helpful: http://blog.dideric.is/2018/03/18/Testing-apollo-containers/

Apollo has something called LinkSchema that makes the first approach Carlos mentioned a lot easier. It still takes some setup, but I think it's worth it. If you're creating responses manually, you have to worry a lot more about keeping your tests up to date/getting false positives when the schema changes and you haven't accounted for it in your code.

Loktopus
  • 462
  • 2
  • 12