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

server.use() or server.resetHandlers do not appear to be working within/between tests #251

Closed
mwarger opened this issue Jun 25, 2020 · 28 comments
Labels
bug Something isn't working needs:reproduction scope:node Related to MSW running in Node

Comments

@mwarger
Copy link

mwarger commented Jun 25, 2020

Describe the bug

server.use() or server.resetHandlers do not appear to be working within tests.

I have a setupTests file that is appropriately calling resetHandlers (confirmed with logging) after each test.

When using a server.use statement to override a route payload like so:

server.use(
      rest.get('myUrl', (req, res, ctx) => {
        return res(ctx.json([]));
      })
    );

I would expect the result to return as an empty array. My main handlers file is setup similarly:

rest.get('myUrl', (req, res, ctx) => {
    return res(ctx.json(mockDataArray));
  })

When running all my tests, I see my mock data, and can log it to the console in the actual component implementation.

When running as part of a test suite, I do not get the overridden empty array that I want, I see the original list from the handlers.

The bug I think I've found is that if I run the test as it.only then I see the correct empty array and my test passes. My component logs out the empty array as the fetched data I was expecting. Running the tests completely by itself seems to fix the issue.

I'm hoping this is just a silly mistake on my part.

Environment

  • msw: 0.19.5
  • nodejs: 12.16.1
  • npm: 6.13.4

Please also provide your browser version. n/a

To Reproduce

Steps to reproduce the behavior:
I attempted to reproduce this behavior using the react example project, but haven't been able to yet.

Expected behavior

I have a setupTests file that is appropriately calling resetHandlers (confirmed with logging) after each test. When using server.use to setup a route override, I should get the payload assigned (as one would expect from the docs as well as the example project.

@mwarger mwarger added the bug Something isn't working label Jun 25, 2020
@marcosvega91
Copy link
Member

By @mwarger thanks for opening this issue :)

Have you tried to understand if you have something not clean before each test?

I think that a reproduction case could help a lot

Thanks for using MSW 🎊

@kettanaito kettanaito added needs:reproduction scope:node Related to MSW running in Node labels Jun 26, 2020
@ZwaarContrast
Copy link

I've been running into the same issue as well. Let me try and make a reproduction.

@kettanaito
Copy link
Member

Hey! Thanks for reaching out with this. Let me share some insights on what may help in triaging this issue.

Ensure single server instance

The list of request handlers (rest.*) is bound to a server instance. Calling .use() and .resetHandlers() operates on the respective instance. Here's the recommended way of setting up the server for tests:

// src/handlers.js
import { rest } from 'msw'

export const handlers = [
  rest.get('myUrl', (req, res, ctx) => res(ctx.json({ a: 1 }))
]
// src/mocks/server.js
import { setupServer } from 'msw/node'
import { handlers } from './handlers'

export const setupServer(...handlers)
// src/setupTests.js
import { server } from './mocks/server'

beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())
// test/MyComponent.test.js
// Reference the same instance created in `mocks/server.js`
import { server } from '../src/mocks/server'

test('regular', () => {})

test('override', () => {
  server.use(...)
})

Isolate

Isolating a single test to fix an issue is usually a sign of shared state between tests. Apart from the list of request handlers, there may be other state at place, which we can find out. What I'd usually to is .skip each test one-by-one, and see what makes the problematic test pass. That does not mean the skipped tests is the issue, but it may lead us to finding the root cause of it.

Please, try to set up a minimal reproduction scenario/repository, so we could take a look. Thank you.

@roydendro
Copy link

roydendro commented Jun 26, 2020

Reproduction project here: https://github.com/roy-dendro/msw

You can run yarn and yarn run test.

You can see in https://github.com/roy-dendro/msw/blob/master/index.spec.js#L23 that i add a console.log in the overwrite graphql call, but its never called.

In the second test, where it supposed to return an error from the msw, it renders as normally with the handler defined in mocks/handlers

@roydendro
Copy link

roydendro commented Jun 26, 2020

Isolate

Isolating a single test to fix an issue is usually a sign of shared state between tests. Apart from the list of request handlers, there may be other state at place, which we can find out. What I'd usually to is .skip each test one-by-one, and see what makes the problematic test pass. That does not mean the skipped tests is the issue, but it may lead us to finding the root cause of it.

Please, try to set up a minimal reproduction scenario/repository, so we could take a look. Thank you.

Please see the reproduction above. Like you mentioned, when skipping the first it in https://github.com/roy-dendro/msw/blob/master/index.spec.js, the second test, and thus the overwrite using server.use does seem to work.

Any idea what could cause that?

@kettanaito
Copy link
Member

Thanks for getting back at such a short notice. We'll checkout your reproduction repository and let you know on any technical insights we find. Stay tunned.

@marcosvega91
Copy link
Member

marcosvega91 commented Jun 26, 2020

Hi @roy-dendro I think that ApolloClient is caching you responses. So after the first test your response is cached and not executed again.

Whenever Apollo Client fetches query results from your server, it automatically caches those results locally. This makes subsequent executions of the same query extremely fast.

@roydendro
Copy link

Hi @roy-dendro I think that ApolloClient is caching you responses. So after the first test your response is cached and not executed again.

Unfortunately i don't think that that's the issue. Setting a fetch-policy to network-only should resolve the issue which is not the case. Also because the whole ApolloProvider is re-rendered across different tests i don't believe cache should be shared between them. Let me verify my assumptions though 😅

@marcosvega91
Copy link
Member

I have tried as above and it works. Let me know if I'm wrong

afterEach(() => {
  client.resetStore()
  // Reset any runtime handlers tests may use.
  server.resetHandlers()
  console.log('server.resetHandlers')
})

@roydendro
Copy link

I have tried as above and it works. Let me know if I'm wrong

afterEach(() => {
  client.resetStore()
  // Reset any runtime handlers tests may use.
  server.resetHandlers()
  console.log('server.resetHandlers')
})

That indeed seems to be working, sorry for disregarding your solution so soon in my previous reply.

Setting the fetchPolicy to network-only didn't resolve it, which confuses me at the moment. Also when trying to reproduce in the browers with multiple ApolloProviders i'm not getting shared cache.

I will look into it some more next week, I don't have time at the moment to look into it :)

thanks for your help.

@marcosvega91
Copy link
Member

it's not a problem :) the important thing is that the problem is solved. We are here trying to help other and make others help us 😄

@mwarger
Copy link
Author

mwarger commented Jun 26, 2020

This ended up being a cache issue. I was using SWR for the tests in question and I needed to add cache clearing and deduping to fix it. Thank you for walking through the issues to consider, and thanks for @roy-dendro for the reproduction and jogging my mind that I needed to think about the cache!

Here's what I ended up doing that solved it: vercel/swr#231 (comment)

I appreciate your help and quick response.

@mwarger mwarger closed this as completed Jun 26, 2020
@dgurns
Copy link

dgurns commented Oct 1, 2020

This bit me as well. Thank you for the solution. I imagine using Apollo Client is pretty common with msw so might be nice to include that detail somewhere in the docs. Happy to PR that if you want.

@philals
Copy link

philals commented Nov 2, 2020

For anyone coming to this who is using react-query. You will need to add

import { queryCache } from "react-query"; 

beforeEach(() => {
  // Resets React Query cache
  queryCache.clear();
});

@kettanaito
Copy link
Member

We should really create an FAQ point mentioning cache invalidation for such request clients.

@kettanaito
Copy link
Member

References this suggestion in this FAQ section.

@vaibhav-systango
Copy link

vaibhav-systango commented Nov 30, 2021

@kettanaito @philals @dgurns @marcosvega91 @roy-dendro I'm unable to use two server.use() get API success call instances

      server.use(
        rest.get(config.apiURL + '/api/steps/Step1', (req, res, ctx) => {
          return res(
            ctx.json(getStepSuccess)
          )
        })
      )

Here I'm conditionally unordered rendering list item or a select field depending on the items being returned in the GET API endpoint, but I'm unable to use the response in two different describe blocks, mock server just renders the whichever block is written first instead of running both the API get calls

Step1.test.js

import * as React from 'react'
import { rest } from 'msw'
import { cleanup, render, waitFor, fireEvent } from '@testing-library/react'
import { Router, Switch } from 'react-router-dom'
import { Provider } from 'react-redux'
import { createMemoryHistory } from 'history'

import { server } from '../../../../mocks/server'
import { config } from '../../../../config/config'

beforeAll(() => server.listen())
afterEach(() => {
    cleanup()
    server.resetHandlers()
})
afterAll(() => server.close())

describe('Step1 integration tests', () => {
  const history = createMemoryHistory()

  const getMountedInstance = () => {
    return render(
      <Provider store={store}>
        <Router history={history}>
          <Switch>
            <Step1 />
          </Switch>
        </Router>
      </Provider>
    )
  }

  describe('Step1: Greater then or equals to 9 Option screen', () => {
    beforeEach(() => {
      cleanup()
      jest.clearAllMocks()
      server.use(
        rest.get(config.apiURL + '/api/steps/Step1', (req, res, ctx) => {
          return res(
            ctx.json(getStep1Success)
          )
        })
      )
      history.location.pathname = ACTIVE_ROUTE
    })
    it('Should handle success on submit button with select ', async () => {
      const { findByLabelText, findByRole } = getMountedInstance()
      const button = await findByRole('button', { name: 'Continue' })
      const select = await findByLabelText(SELECT_LABEL)
      server.use(
        rest.post(config.apiURL + '/api/steps/Step1', (req, res, ctx) => {
          return res(
            ctx.json(postStep1Success)
          )
        })
      )
      expect(button).toBeDefined()
      expect(select).toBeDefined()
      fireEvent.change(select, {
        target: {
          value: UPDATED_ID
        }
      })
      expect(select.value).toBe(UPDATED_ID)
      expect(history.location.pathname).toBe(ACTIVE_ROUTE)
      fireEvent.click(button)
      await waitFor(() => {
        expect(history.location.pathname).toBe(NEXT_STEP_ROUTE)
      })
    })
  })

  describe('Step1: Less then 9 Option screen', () => {
    beforeEach(() => {
      cleanup()
      jest.clearAllMocks()
      server.use(
        rest.get(config.apiURL + '/api/steps/Step1', (req, res, ctx) => {
          return res(
            ctx.json(getStep1SuccessOptionLessThan9)
          )
        })
      )
      history.location.pathname = ACTIVE_ROUTE
    })

    it('Should show selectable list when option less then 9', async () => {
      const { findByRole } = getMountedInstance()
      const selectabelList = await findByRole('radio')
      expect(selectabelList).toBe(1)
    })
  })
})

server.js

import { setupServer } from 'msw/node'
import { handlers } from './handlers'

// This configures a request mocking server with the given request handlers.
export const server = setupServer(...handlers)

handlers.js

// src/mocks/handlers.js
import { rest } from 'msw'

export const handlers = [
  rest.post(apiURL + '/api/flow', (req, res, ctx) => {
    return res(
      ctx.json({ 
  "name": "Step1" 
})
    )
  })
]

@kettanaito
Copy link
Member

Hey, @vaibhav-systango. Id' advise calling server.use() early in your test. Right now you call it after rendering your component. This means that any requests it does on the initial render will not be affected by the runtime handlers you've added. Keep it as a rule of thumb to call server.use() as the first thing in the test (hey, it'd also make such overrides easier to find!).

Otherwise, I'd kindly ask you to prepare a reproduction repository for your issue so I'd be able to help you.

@nicholascm
Copy link

nicholascm commented Dec 9, 2021

I'm seeing a similar issue recently. I have 2 tests in a file ( A and B ), the first test uses the standard handlers (no server.use) , the second uses a server.use handler. Test case B always fails because it is using the standard handlers and disregarding server.use . If I comment out the test case A, then test case B passes.

server.use is the very first line in test case B.

We have this configuration for our project as well:

beforeAll(() => {
  // Enable the mocking in tests.
  server.listen();
});

afterEach(() => {
  // Reset any runtime handlers tests may use.
  server.resetHandlers();
});

afterAll(() => {
  // Clean up once the tests are done.
  server.close();
});

@nicholascm
Copy link

I went about creating a repro repository...and of course...it works. There is something about our setup that is wrong. Disregard above.

@mrguiman
Copy link

mrguiman commented Dec 14, 2021

Hi, I'd like to add to this, because just like @nicholascm, I've encountered the error and created a repo... where it didn't happen. However I did go through with more testing, and I was able to reproduce a case which I cannot explain right now.

https://github.com/MrGuiMan/msw-handler-override

here you can see that the test that overrides mygraphql query doesn't work.
Skipping the test before it allows it to pass, and moving the third test before it does allow it to pass too.
The mechanics of it elude me completely. My best guess is that the first call starts an outstanding request that causes a race condition, but that's as far as my udnerstanding goes.

@kettanaito any insight would be very appreciated. Thanks

@mrguiman
Copy link

mrguiman commented Dec 15, 2021

Alright, brand new day, and I found a solution that I'm okay with.

This -> vercel/swr#781 is what actually helped me.

It turns out waiting on the cache reset seems to be necessary

beforeEach(async () => { await waitFor(() => { client.cache.reset(); }); });

I think it's worth a mention in the FAQ section regarding caching ?

@MindfulBell
Copy link

I cant tell you folks how long it took me to get through this bug. Your soultion(s) did it. Thanks much!

@mkhoussid
Copy link

mkhoussid commented Jun 7, 2022

It turns out waiting on the cache reset seems to be necessary

beforeEach(async () => { await waitFor(() => { client.cache.reset(); }); });

It's in the type definitions

public abstract reset(options?: Cache.ResetOptions): Promise<void>;

@bsides
Copy link

bsides commented Jul 4, 2022

For anyone coming to this who is using react-query. You will need to add


import { queryCache } from "react-query"; 



beforeEach(() => {

  // Resets React Query cache

  queryCache.clear();

});

The api from react query requires you to define a "new" cache in entry point. How do you reset it in every test iteration?

@jvandenaardweg
Copy link

jvandenaardweg commented Sep 16, 2022

The api from react query requires you to define a "new" cache in entry point. How do you reset it in every test iteration?

Just export the new QueryClient your React Query QueryClientProvider uses and use that one in your tests, like so:

beforeEach(() => {
  queryClient.clear();
});

Worked for me.

Or, make sure to use queryClient.clear(); before you set any new (updated) handler in between tests, like below. Or else, React Query seems to pick something from the cache.

it('should do something', async () => {
  queryClient.clear();
  
  server.use(
    rest.get('/some/endpoint', (_req, res, ctx) => {
      return res.once(ctx.json({ data: 'updated data' }));
    }),
  );

  render(<Component />);

  // Your tests that use the updated data
});

Hope this helps others, struggled with this for too long :')

@hrmJ
Copy link

hrmJ commented Oct 11, 2022

Hi @roy-dendro I think that ApolloClient is caching you responses. So after the first test your response is cached and not executed again.

Whenever Apollo Client fetches query results from your server, it automatically caches those results locally. This makes subsequent executions of the same query extremely fast.

I was strugglin with this in relation to RTK query. Indeed, the cached requests were my problem, too.

This thread helped me ro realize that. The solution for RTK query is to do

    store.dispatch(api.util.resetApiState())

between my tests

@rafaelalmeidatk
Copy link

rafaelalmeidatk commented Jul 17, 2023

If you are using react-query I found the simplest solution to be setting the cacheTime to 0 when creating the query client for the tests, so you don't need to clear the cache on every test:

const queryClient = new QueryClient({
  defaultOptions: { queries: { cacheTime: 0 } },
})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working needs:reproduction scope:node Related to MSW running in Node
Projects
None yet
Development

No branches or pull requests