React-apollo: Mutation Error causes tests to fail

Created on 18 Jan 2019  路  15Comments  路  Source: apollographql/react-apollo

According to the docs, I should be able to to mock graphql errors in order to test.

To simulate GraphQL errors, simply define errors along with any data in your result.

const dogMock = {
  // ...
  result: {
    errors: [{ message: "Error!" }],
  },
};

However the error is causing my test to fail outright. It's working as expected for a Query but the docs say it works the same for a Mutation as well.

For the sake of simplicity, the error case for mutations hasn鈥檛 been shown here, but testing Mutation errors is exactly the same as testing Query errors: just add an error to the mock, fire the mutation, and check the UI for error messages.

Intended outcome:
I expected the test to pass and render the error message like it does for a query.

Actual outcome:
The test fails due to the graphql error.

    GraphQL error: There was an error
      at new ApolloError (node_modules/src/errors/ApolloError.ts:56:5)
      at ...

How to reproduce the issue:
Here is an example component and test. As I noted in the test's code, the test fails before I ever get to check if the error message was rendered to the page.

Component

class Example extends Component {
  state = {email: ''};

  render() {
    return (
      <div>
        <Mutation mutation={EXAMPLE_MUTATION} variables={this.state}>
          {(signin, {error, loading}) => {
            if(error)
              return <p data-test="graphql-error">{error.message}</p>;

            return (
              <form method="post" onSubmit={async e => {
                e.preventDefault();
                await signin();
                this.setState({email: ''});
              }}>
                <fieldset disabled={loading} aria-busy={loading}>
                  <label htmlFor="email">
                    Email
                    <input type="email" name="email" placeholder="email" value={this.state.email} onChange={this.saveToState} />
                  </label>
                  <div>
                    <input type="submit" value="Example"/>
                  </div>
                </fieldset>
              </form>
            )
          }}
        </Mutation>
      </div>
    );
  }

  saveToState = e => this.setState({[e.target.name]: e.target.value});
}

Mutation

const EXAMPLE_MUTATION = gql`
  mutation EXAMPLE_MUTATION($email: String!){
    example(email: $email){
      email
      name
    }
  }
`;

Test

describe('<Example />', () => {
  it('handles errors properly', async () => {
    const wrapper = mount(
      <MockedProvider mocks={[{
        request: {query: EXAMPLE_MUTATION, variables: {email: '[email protected]'}},
        result: {errors: [{message: "There was an error"}]}}]}>
        <Example />
      </MockedProvider>
    );

    type(wrapper, 'email', '[email protected]');
    wrapper.update();
    wrapper.find(`form`).simulate('submit');

    // form is busy + contains inputs
    expect(toJSON(wrapper.find('form'))).toMatchSnapshot();

    await wait();
    wrapper.update();

    // test fails before ever getting here

    const err = wrapper.find('[data-test="graphql-error"]');
    expect(err.text()).toEqual('There was an error');

    console.log(err.debug())
    console.log(err.text())
  });

Version

has-PR has-reproduction

Most helpful comment

I've also noticed I'm getting flaky specs when using await wait(); to wait for updates while using react-testing-library. Short of manually waiting for 500ms or something, anyone have any other ideas? Sticking random waits in places to get the mutation queries to finish feels really hacky.

All 15 comments

Thanks @DPflasterer, since you have the code, it makes sense for you to go ahead and PR this as a breaking test. That will speed a resolution.

I stumbled upon the same problem today and debugged that error is thrown if you ommit onError prop in your <Mutation>.

So, replacing your component with

<Mutation mutation={EXAMPLE_MUTATION} variables={this.state} onError={() => {})>

should temporarily solve the issue :)

EDIT:
So, this is the line that is throwing the error:
https://github.com/apollographql/react-apollo/blob/820460551339b9c2ca67232141b52d4428969dad/src/Mutation.tsx#L183

And this example mutation test actually uses the fix I suggested, so I think this behaviour might be intended:
https://github.com/apollographql/react-apollo/blob/65a23e8364a76d35317dd9bb71c3ba54780d0abb/examples/mutation/src/AddUser.js#L22

Nevertheless, as you mentioned, docs about testing are very misleading and should be improved.

Which error handling you are talking about? You should still receive errors using error prop if that's what you mean.

Sorry, spoke a bit too soon.

Adding onError={() => {}} changes the behavior, though, as nothing will actually get thrown, so depending on how it's used (e.g. cancelling a form submit) you'll have to make some changes.

If you skip onError you will not catch anything as the error will be thrown from apollo client, not your component (check the code I linked earlier).

If you want to cancel a form submit as you mentioned, you should do it inside onError - I did not say you should leave it as () => {}, I only said that it prevents throwing the error and, as a result, breaking the tests.

I've also noticed I'm getting flaky specs when using await wait(); to wait for updates while using react-testing-library. Short of manually waiting for 500ms or something, anyone have any other ideas? Sticking random waits in places to get the mutation queries to finish feels really hacky.

The shape of your mock is incorrect, it should be:

const dogMock = {
  request: {
    // ...
  },
  error: { message: "Error!" },
};

Reference: https://www.apollographql.com/docs/react/recipes/testing.html#Testing-error-states

I've also noticed I'm getting flaky specs when using await wait(); to wait for updates while using react-testing-library. Short of manually waiting for 500ms or something, anyone have any other ideas? Sticking random waits in places to get the mutation queries to finish feels really hacky.

@mcmillion running into this error too with react-testing-library. Trying the expect as a callback to the await wait() to see if its still flaky.

it('executes gql mutation and closes modal on click', async () => {
    const props = getProps()
    const { getByText } = renderWithThemeProvider(
      <MockedProvider mocks={mocks} addTypename={false}>
        <DismissButton {...props} />
      </MockedProvider>
    )

    fireEvent.click(getByText('Dismiss'))

    await wait(() => expect(props.toggleModal).toHaveBeenCalled())
  })
})

Im using apollo-client 2.5.1 and still experience the same issue. my mock result object has errors object. im using jest and want to capture snapshot with the error block being rendered but instead my test is throwing the error, just like @DPflasterer described it.

do you guys have any solution?

@rosskevin did you ever get that failing test case? this issue is affecting us and I was looking at adding a PR but it kind of looks like maybe you guys are relying on this broken bubbling behavior to test the Mutation component? (https://github.com/apollographql/react-apollo/blob/master/test/client/Mutation.test.tsx#L251).

I have not added a PR for this otherwise it would be linked or closed. Please feel free to introduce one.

I've created a PR for a failing test that I believe should pass according to the docs as outlined above.

Thanks for the PR @DPflasterer. The proper way mocking/simulating a GraphQLError is already tested here:

https://github.com/apollographql/react-apollo/blob/5cb9638ed2ffa184a0f17d5aceb1a8e7a166a1a4/test/client/Mutation.test.tsx#L498-L505

So in this case it's the docs that are wrong. I'll get them updated. Thanks again!

@hwillson That test is not correctly testing this behavior. The problem is that in the component being tested, the mutation is called with doCreateTodo().catch(...), which bypasses the onError prop. The whole purpose of the

<Mutation ...>
  {(mutate, { error }) => if (error) { ... } else { ... }}
</Mutation>

pattern (as i understand it) is that the error would be handled without needing to catch the error when calling the mutation

From your link:
https://github.com/apollographql/react-apollo/blob/5cb9638ed2ffa184a0f17d5aceb1a8e7a166a1a4/test/client/Mutation.test.tsx#L477-L484

From master:
https://github.com/apollographql/react-apollo/blob/master/packages/components/src/__tests__/client/Mutation.test.tsx#L482-L488

If there's anyone else catching up on this now. The error @brandon-leapyear mentioned I think is explained in comment in a separate issue here https://github.com/apollographql/react-apollo/issues/2614#issuecomment-530803601 - helped me understand at the very least why it's handled there.

Was this page helpful?
0 / 5 - 0 ratings