msw: `flushPromises` from `vue-test-utils` must be called multiple times

Discussed in https://github.com/mswjs/msw/discussions/988

Please read discussion first for full details.

<div type='discussions-op-text'>

Originally posted by vincerubinetti November 17, 2021 I have a Vue and vue-test-utils project that I’m trying to use MSW with. I have unit tests for components that call api functions when mounted. Vue exports a function called flushPromises, which is intended to be a simple function you can await to make sure the api calls have returned and the component has finished rendering before continuing with the test.

import { flushPromises, mount } from "@vue/test-utils";

mount(someComponent, mountOptions);

await flushPromises();
await flushPromises(); // <-- need this extra call for MSW to work properly, but not axios-mock-adapter

// some assertions

The component that I’m testing looks like this:

export default defineComponent({
  data() {
    return {
      status: null
    };
  },
  async mounted() {
    this.status = "loading";
    try {
      this.blah = await axios.get(...blah...);
      this.status = null;
    } catch (error) {
      this.status = "error";
    }
  },
});

For some reason, with MSW, I need two flushPromises calls for my tests to pass. The odd thing is, I was using axios-mock-adapter before trying out MSW, and it was working with just one call. Can anyone think of any reason why MSW might be different?

Another funny thing is that MSW is working in my e2e tests in Cypress (setupWorker), but the problem I describe above is happening in my unit tests in Jest (setupServer), which is setup like this:

const server = setupServer(...handlers);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
</div>

Minimum reproducible example

Here is a barebones Vue CLI project with jest unit tests and cypress e2e tests set up: https://github.com/vincerubinetti/msw-test (archived for posterity)

You can run the unit tests with yarn test:unit and the e2e (with gui) tests with yarn test:e2e. This demonstrates the behavior that msw needs two calls to flushPromises whereas axios-mock-adapter only needs one.

You can verify that msw and axios-mock-adapter are both correctly mocking the call by adding expect(wrapper.text()).toMatch("foo"); expect(wrapper.text()).toMatch("bar");.

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 21 (6 by maintainers)

Most upvoted comments

Summary, if I may, for anyone stumbling upon this in the future:

  • vue-test-utils implements flushPromises as a dead simple way to “wait for external promises to resolve”
  • but flushPromises makes a fairly important assumption about the external promises
  • most api mocking libraries conform to this assumption, but msw does not
  • it is not a race condition. given the current msw implementation, precisely 2 calls are needed.
  • msw dev will not change their implementation to conform (possibly justified if it will cause unexpected breaking changes)
  • vue-test-utils devs will add documentation update with a warning about vue-test-utils + msw

Feel free to edit this comment if any of this is incorrect.

and simply prematurely solves an actual race condition

For a race condition, it has been extraordinarily consistent, across multiple OS’s, new/old devices, and 100+ runs.

I asked the vue-test-utils folks about this. See https://github.com/vuejs/test-utils/issues/137 I also looked into msw’s compiled code, and it seems like every request is behind a setTimeout(func, 0) promise (or more, if using the delay() feature), which is basically like one instance of flushPromises. As such, their explanation for why you need two flushPromises makes more sense to me.

I assume when you say “without MSW” you imply you’re using another API mocking library.

Again yes, axios-mock-adapter. Perhaps that library is mocking things synchronously somehow, leading to the difference.


It may be that the way msw is implemented is inherently incompatible with flushing promises in this way, and if that’s the case it should be noted somewhere.

Ultimately it seems to come down to this. I’m begging for msw and vue-test-utils to just add a simple note somewhere. One sentence could’ve saved me hours and hours of discussion, investigating, making min-reproductions, etc.

I can see that you disagree with vue-test-utils’s approach, but it is the default testing library for vue-cli projects (equivalent to the ubiquitous create-react-app). In other words, a ton of people use it. I would really appreciate it if you could just add a simple warning about using vue-test-utils with msw.

I’ve just written a new Vue + MSW integration example and I highly recommend anybody reading this to use wait-for-expect to handle asynchronous code. You shouldn’t rely on third-party packages to flush promises for you. That brings too much implementation details to your tests and you start focusing on the wrong thing.

import waitFor from 'wait-for-expect'

it('my test', async () => {
  await waitFor(() => {
    expect(someAsyncState).toBe(expectedResult)
  })
})