apollo-client
apollo-client copied to clipboard
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
you need to pass it the query, variables and expected result
I am attempting to upgrade to @apollo/client
with today's new release and having the same issue with tests for loading states.
+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
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.
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.
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.
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).
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 Reactact()
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. 😁
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.
Same here, hundreds of our tests failed because of this issue. Please bring back the old behavior.
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...');
});
+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...
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()
}
@MattBred thank you so much man! saved me
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 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.
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.
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!
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!
@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.
@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 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.
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.