-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
MockedProvider causing test to complain about not using act()
.
#5920
Comments
@donedgardo I've beeing having the same issue, using Jest and @testing-library/react. Right now I'm suppressing those warnings, as described in the in the @testing-library/react documentation. In my jest global setup file, I've added const originalError = console.error;
beforeAll(() => {
console.error = (...args: any[]) => {
if (/Warning.*not wrapped in act/.test(args[0])) {
return;
}
originalError.call(console, ...args);
};
});
afterAll(() => {
console.error = originalError;
}); I'm not completely sure about this approach (this might suppress warnings that need to be taken care of), but that does the trick! 😄 |
@donedgardo Thanks for pointing that issue out. I've been using the import { render, wait } from "@testing-library/react";
it ("renders without errors", () => {
render(
<MockedProvider mocks={mocks} addTypename={false}>
<ResearchCategoryList category="Education" />
</MockedProvider>
);
await wait()
// run expectations
}); In your case, I think you can try and wrap the await act(wait); Both solutions work for me! 🎉 |
Does anyone know how to test "loading..." States, then? import { mount } from "enzyme";
it('renders loading', () => {
let component = mount(
<MockedProvider mocks={mocks} addTypename={false}>
<ResearchCategoryList category="Education" />
</MockedProvider>
)
// promise not resolved yet
expect(component.render()).toMatchSnapshot()
}); Leads into error This test is fine, but will not test the loading state as the promise is already resolved import { mount } from "enzyme";
import { wait } from "@testing-library/react";
it('renders loading', async () => {
let component = mount(
<MockedProvider mocks={mocks} addTypename={false}>
<ResearchCategoryList category="Education" />
</MockedProvider>
)
await wait(); // promise resolves
expect(component.render()).toMatchSnapshot()
}); |
Hi @Frozen-byte, I suggest you use the import { render, wait } from "@testing-library/react";
it("renders loading", async () => {
const { container } = render(
<MockedProvider mocks={mocks} addTypename={false}>
<ResearchCategoryList category="Education" />
</MockedProvider>
);
// take a snapshot of the loading state
expect(container).toMatchSnapshot();
// wait for content
await wait()
// take a snapshot of the rendered content (error or data)
expect(container).toMatchSnapshot();
}); Does that help you? |
Yes that helped a lot! Here is my not simplified working code, without enzyme: import { render, wait } from "@testing-library/react";
describe("render correctly", () => {
it("should resolve loading state", async () => {
const { container } = render(
<MockedProvider
mocks={[
GetAccountMock,
GetActivityMock,
GetSubmittedPriceInquiryProductsMock,
GetNewPriceInquiryProductsMock,
UpdateNewPriceInquiryItemsMock
]}
addTypename={false}
>
<PriceInquiryForm params={{ inquiryId: "2" }} />
</MockedProvider>
);
// loading
expect(container.firstChild.classList.contains("loading")).toBe(true);
expect(container.firstChild).toMatchSnapshot();
await wait();
// content
expect(container.firstChild.classList.contains("loading")).toBe(false);
expect(container.firstChild).toMatchSnapshot();
});
}); // edit: as I tried to split the loading and content part into two assertions I noticed that it is obligatory to |
I feel as though this may be caused by #5869 Here's the code I had to write to alleviate the problem, and the test case shows the problem. Essentially, you need to import wait from 'waait';
const Component = ({ spy }) => {
// this hook does a `useQuery` with `cache-only`
const { loading, data } = useLocalFilterState();
spy(loading, data);
return null;
};
const spy = jest.fn();
await act(async () => {
mount(
<ApolloProvider client={client}>
<Component spy={spy} />
</ApolloProvider>
);
// fixes React warning that "An update to Component inside a test was not wrapped in act(...)."
// see below comment on why
await wait(0);
});
// TODO "loading" (first param) should be false, and there should be one render,
// but there's a bug in Apollo 3.0
// @see https://github.com/apollographql/apollo-client/issues/5869
// assert that an object is returned as "data" (second param) which is all we care about
expect(spy).toHaveBeenCalledTimes(2);
expect(spy).toHaveBeenNthCalledWith(1, true, expect.any(Object));
expect(spy).toHaveBeenNthCalledWith(2, false, expect.any(Object)); |
With it("should render without errors", async () => {
const { container } = render(
<MockedProvider>
<MyView />
</MockedProvider>
);
await waitFor(() => {});
expect(container).toMatchSnapshot();
}); |
It might be a bit of an overkill, but I hate waiting a random number of seconds so I just hooked some deferred promises on the mocks so I can wait for them to finish. I did this mostly because in our project we are using multiple apollo providers so we had random failing tests due to CPU differences between local and CI servers. I hope this helps, and hopefully the apollo team will include something like this in a future release using the internal
import PropTypes from "prop-types";
import React, { Component, Fragment } from "react";
import { act } from "react-dom/test-utils";
import { useQuery } from "@apollo/react-hooks";
import { MockedProvider as ApolloMockedProvider } from "@apollo/react-testing";
class Deferred {
constructor() {
this.isResolved = false;
this.resolve = null;
this.deferredPromise = new Promise((resolve) => {
this.resolve = () => {
if (!this.isResolved) {
this.isResolved = true;
resolve();
}
};
});
}
promise = () => this.deferredPromise;
wait = () => act(this.promise);
}
const Hook = ({ mock }) => {
const { loading, error, data } = useQuery(mock.request.query);
if (!mock.promises) {
mock._deferredPromises = {
loading: new Deferred(),
error: new Deferred(),
data: new Deferred(),
};
mock.promises = {
loading: mock._deferredPromises.loading.wait,
error: mock._deferredPromises.error.wait,
data: mock._deferredPromises.data.wait,
};
}
if (loading) {
mock._deferredPromises.loading.resolve();
}
if (error) {
mock._deferredPromises.error.resolve();
}
if (data) {
mock._deferredPromises.data.resolve();
}
return null;
};
export class MockedProvider extends Component {
static propTypes = {
children: PropTypes.any.isRequired,
addTypename: PropTypes.bool,
mocks: PropTypes.array,
}
static defaultProps = {
addTypename: false,
mocks: [],
}
render() {
const { addTypename, mocks, children, ...props } = this.props;
return (
<ApolloMockedProvider {...props} addTypename={addTypename} mocks={mocks}>
<Fragment>
{mocks.map(this.createHook)}
{children}
</Fragment>
</ApolloMockedProvider>
);
}
createHook = (mock, id) => {
if (!mock.request?.query) {
return null;
}
return <Hook key={id} mock={mock} />;
}
}
import React from "react";
import { mount } from "enzyme";
import { MockedProvider } from "./helpers/tests/mockedProvider";
import MyComponent from "./index";
import query from "./index.gql";
describe("<MyComponent />", () => {
let mocks = [];
beforeEach(() => {
mocks = [{
request: { query },
result: {
// your data
data: { },
},
}];
});
it("renders MyComponent with data", async() => {
const teamPage = mount(
<MockedProvider mocks={mocks}>
<MyComponent />
</MockedProvider>
);
expect(teamPage.isEmptyRender()).toEqual(true);
await mocks[0].promises.data();
teamPage.update();
expect(teamPage.isEmptyRender()).toEqual(false);
});
}); LATER EDIT: I created a gist where I also removed some useless code. You don't need to wait for the loading state to test it and data and error are final states ( or at least in unit testing ). |
If i use how can i test loading state? describe("when loading state", () => {
it("should render spinner component", async () => {
const { getByTestId } = render(
<MockedProvider mocks={[]}>
<SomeComponent />
</MockedProvider>,
);
await waitFor(() => {
/**
* This test is failed.
* if use await, in an error state because the data is already fetched.
*/
expect(getByTestId("spinner")).toBeVisible();
});
});
}); |
@kkak10 try this describe("when loading state", () => {
it("should render spinner component", async () => {
const { getByTestId } = render(
<MockedProvider mocks={[]}>
<SomeComponent />
</MockedProvider>,
);
await new Promise(resolve => setTimeout(resolve, 0));
expect(getByTestId("spinner")).toBeVisible();
});
}); from the docs |
@mlg87 i try your code, but fail that same result as my code. |
if i not use |
Found the solution! The problem is
|
I just created a wrapper of render to handle this
Then you can just
Be sure to also stick a
|
For anyone else that comes across this I used the following
|
@ryan-rushton, that's a misuse of |
@DylanRJohnston that is exactly the point. It is a hack but it stops log spam in my tests. When I just have From my understanding we are waiting for apollo client to update its internal state from loading to the mocked final state. By using the Another alternative that works is Edit: While I think of it it would be nice if the API had a |
still having that same issue... problem now is, sometimes the tests work without a problem, but sometimes they don't and they will throw that |
I'm running into the same issue as @ryan-rushton. We have props that are changing as side effects of the client completing mocked queries, switching out of "loading" state – and need to get to the "success" state to test other subsequent conditions. The documented way still throws the I'm personally going with the |
There is a lot of history in this issue, and the problems listed head in all sorts of different directions. If anyone thinks this is still a problem in |
It doesn't support to test |
Here same would be nice to have an official |
I am still getting this issue, with all latest versions of "@testing-library/react": "^12.1.2" Sample
The only way to get rid off this currently is below code to handle error and I do not like this.
|
@hwillson I used the example from your docs. Go here and run |
Possible approach for import React from 'react';
import { NetworkStatus } from '@apollo/client';
import { mount, ReactWrapper } from 'enzyme';
import { act } from 'react-dom/test-utils';
import { CustomMockedProvider, CustomMockedProviderProps } from './CustomMockedProvider';
import sleep from '../sleep';
export const mountMocked = async (
cmp: React.ReactElement,
{ timeout, ...providerProps }: CustomMockedProviderProps & { timeout: number } = { timeout: 5000 },
) => {
let w: ReactWrapper;
await act(async () => {
w = mount(<CustomMockedProvider {...providerProps}>{cmp}</CustomMockedProvider>);
if (!w) {
throw new Error('MountMocked: Unable to mount given component');
}
const mockedProvider = w.find('MockedProvider');
if (!mockedProvider) {
throw new Error('MountMocked: given component does not use Apollo MockedProvider');
}
const start = Date.now();
const hasSettled = () => {
w.update();
if (Date.now() - start > timeout) {
throw new Error(`MountMocked: Pending queries have not settled after ${timeout}ms, aborting the test.`);
}
const queryMap = mockedProvider.state()?.client?.queryManager?.queries ?? new Map();
const queries = Array.from(queryMap.values());
if (queries.every((q) => q.networkStatus === NetworkStatus.ready)) {
return true;
}
return false;
};
do {
await sleep(5);
} while (!hasSettled());
w.update();
});
// We're either throwing or
// it's guaranteed to be present.
return w!;
};
export default mountMocked; Essentially, we're just waiting up until ApolloClient from the Then, in your tests, you're basically using |
Intended outcome:
Actual outcome:
When running test using MockProvider I expect test to not error out with the following error:
How to reproduce the issue:
The following test will cause test to complain:
Versions
"@apollo/react-testing": "^3.1.3",
The text was updated successfully, but these errors were encountered: