apollo-client: Can't test loading state using MockedProvider

Intended outcome: I tried to test loading state

import {act, cleanup, render} from 'react-native-testing-library';
import { MockedProvider } from '@apollo/client/testing';

const {getByTestId} = render(
    <MockedProvider mocks={[]} addTypename={false}>
      {<ProductsScreen {...props} />}
    );
    await act(() => wait(0));
    expect(getByTestId('ActivityIndicator')).not.toBe(null);

Actual outcome: but it gives me an error No more mocked responses for the query

Versions System: OS: macOS 10.15.3 Binaries: Node: 10.18.1 Yarn: 1.21.1 npm: 6.13.6 Browsers: Chrome: 83.0.4103.97 Safari: 13.0.5 npmPackages: @apollo/client: ^3.0.0-rc.0 => 3.0.0-rc.0

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 22
  • Comments: 23 (4 by maintainers)

Most upvoted comments

I found a super simple solution to the problem. Just use a very high delay value for the mocked response. This keeps the query indefinitely long in the loading state.

Example:


const myQueryMock: MockedResponse<ResourcesPageConfigQuery> = {
  request: { query: MY_QUERY_DEFINITION, variables: { /* ... */ } },
  result: { data: { /* ... */ } },
  delay: 100000000000000, // This will keep the query in "loading" state for ~3170 years
};;

const underTest = () => (
  <MockedProvider mocks={myQueryMock}>
    <MyComponent />
  </MockedProvider>
);

// ...

This behavior is even suggested in the documentation, though of course also giving the same Error: https://www.apollographql.com/docs/react/development-testing/testing/#testing-loading-states

it('should render loading state initially', () => {
  const component = renderer.create(
    <MockedProvider mocks={[]}>
      <Dog />
    </MockedProvider>,
  );

  const tree = component.toJSON();
  expect(tree.children).toContain('Loading...');
});

Same here, hundreds of our tests failed because of this issue. Please bring back the old behavior.

+1

I have same issue on @apollo/client 3.0.1 version

 it("should render loading spinner", () => {
      const { getByTestId } = render(
        <ApolloMockedProvider mocks={[]} addTypename={false}>
          <SomeComponent />
        </ApolloMockedProvider>,
      );

      expect(getByTestId("Spinner")).toBeVisible();
    });

this test code is failed. and error message

   Error: Uncaught [Error: No more mocked responses for the query: query xxxx

Hitting this while upgrading my project, it would be nice if there was a MockProvider mode that operated in a less strict mode, as it did prior to the update.

I have a workaround that appears to be working for me at the moment on @apollo/client v3.0.2.

Using the OPs code as a starting point (NOT WORKING):

import {act, cleanup, render} from 'react-native-testing-library';
import { MockedProvider } from '@apollo/client/testing';

const {getByTestId} = render(
    <MockedProvider mocks={[]} addTypename={false}>
      {<ProductsScreen {...props} />}
    );
    await act(() => wait(0));
    expect(getByTestId('ActivityIndicator')).not.toBe(null);

I recommend three changes:

  • use a real mock object that includes a response. This will prevent the No more mocked responses error.
  • expect before wait. We want to run our expectation against the initial state, which is the loading state.
  • act-wait after expect. MockedProvider will update state after our expectation. To keep our test clean of React act() warnings, we need to wrap that state change in act.

All together, that looks like this (WORKAROUND SOLUTION):

import {act, cleanup, render} from 'react-native-testing-library';
import { MockedProvider } from '@apollo/client/testing';
import {myMock} from "./myMocks";

const {getByTestId} = render(
    <MockedProvider mocks={[myMock]} addTypename={false}>
      {<ProductsScreen {...props} />}
    );
    expect(getByTestId('ActivityIndicator')).not.toBe(null);
    await act(() => wait(0));

So those shenanigans get my Apollo-loading tests passing stably in any order again. šŸ™Œ But it would be much nicer if a mock of [] would not throw a No more mocked responses error to begin with. Then there would be no state change to worry about wrapping in act, and no need to sneak in an expectation before the next tick, and testing the loading state would be nice and straightforward again. 😁

Same problem here… Trying to update to @apollo/client and got tens of these ā€œNo more mocked responsesā€ errors. Also in the simple loading state check.

I am attempting to upgrade to @apollo/client with today’s new release and having the same issue with tests for loading states.

FWIW, In my opinion, when MockedProvider receives mocks={[]}, it should detect this as the intentional ā€œloadingā€ input, and just return null without throwing an error. That will keep the provider in the loading state indefinitely, and will save users the current headaches:

  • unwanted errors when intentionally providing no mocked response, which are causing some people failing tests
  • or tests still pass, but with a clutter of unwanted false-positive log messages (this is what is bothering me)

I believe the way to go is to add something like this at line 76 of mockLink.ts (just a guess. I’m not sufficiently knowledgeable about the codebase):

if (!key) return null; // stay in the loading state without logging an error

This creates separation between cases where we want the error (a requested key does not exist in the mock) and when we do not want the error (no keys exist in the mock).

For anyone still experiencing this bug (I am, even with everything up to date) I came up with an interim fix that doesn’t patch anything.

Add a new LoadingApolloLink.js somewhere in your project:

import { __extends } from 'tslib';
import { MockLink } from '@apollo/client/utilities/testing/mocking/mockLink';

var LoadingApolloLink = (function (_super) {
  __extends(LoadingLink, _super);
  function LoadingLink() {
    var _this = _super.call(this) || this;
    return _this;
  }
  LoadingLink.prototype.request = function (operation) {
    return false;
  };
  return LoadingLink;
})(MockLink);

export { LoadingApolloLink };

Now use this link in your provider: Note: I haven’t actually tried this out in tests, only Storybook

<MockedProvider link={LoadingApolloLink}>

My story parameter setup looks like this:

  apolloClient: {
    link: new LoadingApolloLink()
  }