Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't test loading state using MockedProvider #6383

Closed
Doaa-Ismael opened this issue Jun 4, 2020 · 23 comments
Closed

Can't test loading state using MockedProvider #6383

Doaa-Ismael opened this issue Jun 4, 2020 · 23 comments
Labels

Comments

@Doaa-Ismael
Copy link

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

@sgentile
Copy link

you need to pass it the query, variables and expected result

@bottd
Copy link

bottd commented Jul 14, 2020

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

@kkak10
Copy link

kkak10 commented Jul 16, 2020

+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

@kkak10
Copy link

kkak10 commented Jul 28, 2020

I saw the code related to this issue.

when I tested it by attaching @apolo/client to a project newly created with CRA, no issue occurred.

To be exact, I had to go through the error because I didn't have the mock query, but the test didn't fail.

I think Among the parts dealing with Observable, there seems to be a case with and without an error handling.

@obrejla
Copy link

obrejla commented Jul 28, 2020

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.

@kkak10
Copy link

kkak10 commented Jul 28, 2020

https://github.com/apollographql/apollo-client/blob/master/src/utilities/testing/mocking/mockLink.ts#L89

There are times when the test fails and there are times when the error is thrown an error in this part.

@erichochhalter
Copy link

erichochhalter commented Jul 29, 2020

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).

@erichochhalter
Copy link

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. 😁

@JamesMcMahon
Copy link

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.

@mdovn
Copy link

mdovn commented Aug 14, 2020

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

@coupez
Copy link

coupez commented Sep 22, 2020

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...');
});

@Duncan-Alexander-Coutts

+1 I too have followed the documentation at https://www.apollographql.com/docs/react/development-testing/testing/#testing-loading-states. I am using react testing library. The same error about not more requests/responses is raised. This is really annoying...

@MattBred
Copy link

MattBred commented Dec 9, 2020

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()
  }

@louisholley
Copy link

@MattBred thank you so much man! saved me

@foxylion
Copy link

foxylion commented Jun 1, 2021

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>
);

// ...

@riezebosch
Copy link

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>
);

// ...

I also found that advice on the storybook addon for Apollo: https://storybook.js.org/addons/storybook-addon-apollo-client.

@motss
Copy link

motss commented Oct 13, 2022

Just here to contribute what works for me yet simple:

render(<MockedProvider mocks={mocks}><YourComponent /></MockProvider>);

// This should work but it does not. You can always see the snapshot but it is always invisible or not in the document.
// I guess the loading happens almost instantaneously which might cause the assertion to fail but I could be wrong.
// expect(await getByTestId('ActivityIndicator')).toBeInTheDocument();

// Alternatively, you can try `.not.toBeNull()` as a workaround
// expect(await getByTestId('ActivityIndicator')).not.toBeNull();

// How about flush all the updates with act()? It at least works for me.
await act(async () => {
  expect(await getByTestId('ActivityIndicator')).toBeInTheDocument();
});

Hope this helps and works for you.

@alessbell
Copy link
Contributor

Hi everyone 👋 I'm trying to determine whether or not this is still an issue. In the last few months, I've updated the testing examples in our docs to use Testing Library, and the examples cited in the docs can be run via CodeSandbox here.

You can see the tests are using MockedProvider and we can make expect assertions on the loading state:

import "@testing-library/jest-dom";
import { render, screen } from "@testing-library/react";
import { MockedProvider } from "@apollo/client/testing";
import { GET_DOG_QUERY, Dog } from "./dog";

const mocks = [
  {
    request: {
      query: GET_DOG_QUERY,
      variables: {
        name: "Buck"
      }
    },
    result: {
      data: {
        dog: { id: "1", name: "Buck", breed: "bulldog" }
      }
    }
  }
];

it("renders without error", async () => {
  render(
    <MockedProvider mocks={mocks} addTypename={false}>
      <Dog name="Buck" />
    </MockedProvider>
  );
  expect(await screen.findByText("Loading...")).toBeInTheDocument();
});

Can anyone provide a runnable test where this is not the case? I'll leave this open for now to give folks a chance to reply, thanks!

@alessbell alessbell added the 🏓 awaiting-contributor-response requires input from a contributor label Jan 12, 2023
@github-actions
Copy link
Contributor

We're closing this issue now but feel free to ping the maintainers or open a new issue if you still need support. Thank you!

@github-actions github-actions bot closed this as not planned Won't fix, can't repro, duplicate, stale Feb 12, 2023
@radfahrer
Copy link

@alessbell my issue with MockedProvider is that there doesn't seem to be a good way to isolate the loading state. All the examples provided assume that the component will be in both states, first loading and then a response. This is fine most of the time. The exception is when you want to specifically test functionality in the loading state and the assertion is negative.

For example, imagine that you have content that is only shown to authenticate users. There might be a query to see if the current user is authenticated. While in the loading state I would want to expect(await screen.findByText('private content')).not.toBeInTheDocument(). If the content loads anyway, this test would fail. Conversely, if I use a mock that errors instead of just stopping at the loading state, the test will pass, but it would be a false positive.

@dylanwulf
Copy link
Contributor

@radfahrer Take a look at this testing approach: https://www.arahansen.com/testing-apollo-components-using-react-testing-library/
This allows you to return a promise from the mock resolvers, and apollo-client will stay in a loading state until after the promise is resolved.

@radfahrer
Copy link

@radfahrer Take a look at this testing approach: https://www.arahansen.com/testing-apollo-components-using-react-testing-library/
This allows you to return a promise from the mock resolvers, and apollo-client will stay in a loading state until after the promise is resolved.

I ended up mocking useQuery and using requireActual to provide the implementation for my mock. In combination with mockImplementationOnce I was able to get the behavior I wanted to test for this one test case and allow MockedProvider to keep working for the rest of the suite.

@github-actions
Copy link
Contributor

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
For general questions, we recommend using StackOverflow or our discord server.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 18, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests