David M
David M

Reputation: 305

React Apollo MockProvider always loading, never giving data

I'm trying to test a component that uses graphql, but when using Apollo's MockProvider I never get the data, it just says loading = true every time.

A complete, minimalist example is here

Things I've tried:

  1. Looking online (found this similar question, but since it had no answer I thought I'd make a new one with more information)
  2. Tried exporting components without the graphql when testing (export function Component), but that doesn't work when testing nested components
  3. Tried simplifying as much as possible (the results of which is in the example)

Upvotes: 7

Views: 7462

Answers (3)

aaa7c4
aaa7c4

Reputation: 1

Also, make sure that the query that you are importing in component file is the exact same one that you are using in the test file. For example, I had that an issue where my mocks prop wouldn't pass any data to the MockedProvider because I was importing:

import { CURRENT_USER_QUERY } from '../lib/queries';

in PleaseSigin.js, and I was importing:

import { CURRENT_USER_QUERY } from './User';

in PleaseSignin.test.js. Apparently, even though both queries are identical, they are not the same as far as Jest is concerned.

Upvotes: 0

dmwong2268
dmwong2268

Reputation: 3565

I'm not a fan of the await wait(0) approach. Looking at the apollo docs:

For more complex UI with heavy calculations, or delays added into its render logic, the wait(0) will not be long enough.

This means that your tests can potentially be flaky. To solve this issue I use the wait-for-expect package (also covered in the docs: https://www.apollographql.com/docs/guides/testing-react-components.html#Testing-mutation-components):

it('should render the HeroDiv if there is guide data', async () => {
  const wrapper = mount(
    <MockedProvider mocks={mocksWithGuideData} addTypename={false}>
      <Hero {...props} />
    </MockedProvider>
  );

  await waitForExpect(() => {
    wrapper.update();
    expect(wrapper.find('HeroDiv').exists()).toBeTruthy();
  });
})

waitForExpect will essentially poll until the condition is complete and it times out after 5 seconds. This guarantees that your test will complete, as long as your query completes before 5 seconds, which it absolutely should if you're using MockedProvider.

The docs point out one caveat: The risk of using a package like this everywhere by default is that every test could take up to five seconds to execute (or longer if the default timeout has been increased). But in my experience this won't ever happen with MockedProvider. Also aside, await wait(0) would not handle this case consistently anyway.

Upvotes: 2

MikaelC
MikaelC

Reputation: 408

Yup I ran into this issue as well. The funny thing was that if I add console logs to the component itself, I could see that the data ended up getting there just fine. But for some reason the wrapper would still only contain our "Loading ..." UI.

Turns out you need to call wrapper.update() to get the wrapper to rerender it's contents.

This is what worked for me, but it seems less than ideal, so if anyone else has a workaround let me know! Right now our tests look something like:

it('should render the HeroDiv if there is guide data', async () => {
  const wrapper = mount(
    <MockedProvider mocks={mocksWithGuideData} addTypename={false}>
      <Hero {...props} />
    </MockedProvider>
  );

  await wait(0);
  wrapper.update();

  expect(wrapper.find('HeroDiv').exists()).toBeTruthy();
})

Upvotes: 15

Related Questions