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

Using msw with jest has unexpected behaviors with apollo client. #9619

Closed
christo8989 opened this issue Apr 23, 2022 · 11 comments
Closed

Using msw with jest has unexpected behaviors with apollo client. #9619

christo8989 opened this issue Apr 23, 2022 · 11 comments

Comments

@christo8989
Copy link

Intended outcome:

const Component = () => {
  const { data } = useQuery(...)

  if (data) {
    return <div>Has data</div>
  } else {
    return <div>No data</div>
  }
}
const handler = jest.fn((req, res, ctx) => res(ctx.data({...}));
server.use(
  graphql.query('foo', handler)
)

render(<Component />)

await waitFor(() => expect(handler).toHaveReturned())
expect(screen.getByText('Has data')).toBeVisible()

The test should return true.

Actual outcome:

The test returns false because the data has not returned yet.

How to reproduce the issue:

See the first section.

Versions

System:
OS: macOS 11.6.4
Binaries:
Node: 12.22.12 - ~/.nvm/versions/node/v12.22.12/bin/node
npm: 6.14.16 - ~/.nvm/versions/node/v12.22.12/bin/npm
Browsers:
Chrome: 100.0.4896.127
Edge: 100.0.1185.50
Firefox: 84.0.2
Safari: 15.3
npmPackages:
@apollo/client: ^3.3.3 => 3.3.13
apollo-upload-client: ^14.1.3 => 14.1.3

@RealDrewKlayman
Copy link

+1

@jpvajda
Copy link
Contributor

jpvajda commented Jun 9, 2022

Thank you for reporting this! Have you tried posting this in our Apollo Community for guidance from other Apollo users, as other's may have input into your question. I'm curious if anyone has a work around for using MSW with Jest. I'll leave this open as a question for now, and see if the team can provide some direction.

@jpvajda jpvajda added ⁉️ question 🏓 awaiting-team-response requires input from the apollo team labels Jun 9, 2022
@christo8989
Copy link
Author

christo8989 commented Jun 9, 2022

@jpvajda I haven't. If I revisit this I'll follow up there. Thank you.

I suspect there are multiple async operations that happen between returning and having the data in the component.

I don't think this happens with MockedProvider. If I remember correctly, a tick can be used with MockedProvider.

I wonder if after checking if it's been returned, if a single tick would work. 🤔

@bignimbus
Copy link
Contributor

Hi all, I don't think we have enough to say that there's definitely something actionable for the maintainers here. I don't think we can expect an instantaneous render after a network request resolves. So even though the data is available the React render cycle still needs to work. I wonder if this would produce a green test?

const handler = jest.fn((req, res, ctx) => res(ctx.data({...}));
server.use(
  graphql.query('foo', handler)
)

render(<Component />)

const hasDataNode = await screen.findByText('Has data'));
expect(hasDataNode).not.toBeNull();

@bignimbus bignimbus added 🥀 needs-reproduction 🏓 awaiting-contributor-response requires input from a contributor and removed 🏓 awaiting-team-response requires input from the apollo team labels Feb 14, 2023
@szamanr
Copy link

szamanr commented Feb 14, 2023

what works for me is using a custom async function that waits for the next event loop.

~ nextEventLoop.ts
/**
 * triggers the next tick of the event loop. this allows async operations to complete,
 * e.g. populating apollo mocks, resolving a query, resolving the response of a mocked hook. <br />
 * you can use this instead of `waitFor` or `findBy...` queries.
 *
 * @example
 * userEvent.click(something); // perform an action which triggers a new query
 * await nextEventLoop();
 * expect(...); // now the query has resolved
 */
export const nextEventLoop = (): Promise<void> =>
  act(async () => {
    await new Promise((resolve) => setTimeout(resolve, 0));
  });

i have it built into my test render method like this:

~ testRender.tsx
import { render } from "@testing-library/react";
export const testRenderAsync = async (
  component: JSX.Element,
  options?: TestOptions
): Promise<RenderResult> => {
  const rendered = render(component, options);

  // we need to wait until the next tick of the event loop so apollo queries can resolve
  await nextEventLoop();

  return rendered;
};

but you can also use it stand-alone. using your example:

const handler = jest.fn((req, res, ctx) => res(ctx.data({...}));

if("should render component", async () => {
  server.use(
    graphql.query('foo', handler)
  )
  render(<Component />);

  await nextEventLoop();
  expect(screen.getByText('Has data')).toBeVisible();
});

@github-actions github-actions bot removed the 🏓 awaiting-contributor-response requires input from a contributor label Feb 15, 2023
@bignimbus
Copy link
Contributor

Hi all, does the above workaround work for you? (Thanks @szamanr!)

@bignimbus bignimbus added the 🏓 awaiting-contributor-response requires input from a contributor label Mar 16, 2023
@christo8989
Copy link
Author

I haven't tried recently but when I wrote this, ticking next was arbitrary because it would take a random number of ticks.

If I can tick once and be good then I would be happy with that.

@github-actions github-actions bot removed the 🏓 awaiting-contributor-response requires input from a contributor label Mar 17, 2023
@phryneas
Copy link
Member

phryneas commented Mar 17, 2023

(This has already been stated by @bignimbus above, but I thought I'd make it a bit more obvious)

Generally, with anything involving a "network", please use the Async Methods findBy* instead of their synchronous counterparts (getBy*).
Then you also don't need to manually wait a tick (and you also don't have to wait for a handler of any kind).

const handler = jest.fn((req, res, ctx) => res(ctx.data({...}));
server.use(
  graphql.query('foo', handler)
)

render(<Component />)

- await waitFor(() => expect(handler).toHaveReturned())
- expect(screen.getByText('Has data')).toBeVisible()
+ expect(await screen.findByText('Has data')).toBeVisible()

@christo8989
Copy link
Author

True. The one situation I can think of is checking that some element doesn't show up. Where we need to be confident that the data loaded and the components render but the one component we're querying doesn't show up.

@alessbell
Copy link
Contributor

Seconding what @bignimbus and @phryneas wrote above. I'll go ahead and close out this issue, but I'd also like to mention we're currently working on improving the testing story in Apollo Client. You can follow issue #9738 if you're interested. Thanks!

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 Jan 18, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

7 participants