mswjs / examples

Examples of Mock Service Worker usage with various frameworks and libraries.
683 stars 211 forks source link

Not intercepting Apollo GQL requests at all #83

Closed Wozniaxos closed 1 year ago

Wozniaxos commented 1 year ago

Hi! i just want to say that i read all issues with that title and browse through all internet and can't make it work.

Apollo client version: 3.7.4 msw version: 0.49.3

My setup test file:

import { cleanup } from '@testing-library/react';
import { afterEach, beforeAll, afterAll } from 'vitest';
import matchers from '@testing-library/jest-dom/matchers';
import { expect } from 'vitest';
import { server } from './mocks/server';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
HTMLCanvasElement.prototype.getContext = () => ({} as any);

beforeAll(() => {
  server.listen({
    onUnhandledRequest: 'warn',
  });
  server.printHandlers();
});

afterEach(() => {
  cleanup();
  server.resetHandlers();
});

afterAll(() => server.close());

expect.extend(matchers);

My server

// src/mocks/server.js
import { setupServer } from 'msw/node';
import { graphQLHandlers } from './graphQLHandlers';
import { restHandlers } from './restHandlers';
// This configures a request mocking server with the given request handlers.
export const server = setupServer(...graphQLHandlers);

Query: generated by code gen

export const WatchlistDocument = gql`
  query Watchlist {
    watchlist {
      id
      venueAccount
      securityId
      symbol
    }
  }
`;

My client setup

const httpLink = new HttpLink({
  uri: `${import.meta.env.VITE_REST_API_SERVER}/graphql`,
});

export const apolloClient = new ApolloClient({
  cache: new InMemoryCache(),
  link: httpLink,
});

finally my handlers:

import { graphql } from 'msw';

export const graphQLHandlers = [
  graphql.query('Watchlist', (req, res, ctx) => {
   //It is never called
    console.log('querinasdas dasd asd asd asd asd asd asd asd asd asd g!!');
    return res(
      ctx.data({
        watchlist: [
          {
            __typename: 'WatchlistResponse',
            id: '1',
            symbol: 'SHIBUSD',
            venueAccount: 'Bitfinex',
            securityId: '1',
          },
          {
            __typename: 'WatchlistResponse',
            id: '2',
            symbol: 'SHIBUSD',
            venueAccount: 'BitMEX',
            securityId: '2',
          },
        ],
      })
    );
  }),
]

I've even tried with capturing all operation with graphql.operation and none was called. Whatever i try i get undefined from useQuery hook. I also have logged the handler at it prints it out as it exist

Screenshot 2023-01-24 at 18 47 40

There is any fetch mocking in the entire project. Another thing that maybe is worth to mention that we have axios installed - but this one is not taking part in the test (not imported in any place)

I've also investigated the query that is dispatched during this test:

Screenshot 2023-01-24 at 18 58 04

The thing is that there is nothing custom and different here than just "Get started" apollo setup + GraphQL mock setup 1:1 as per MSW docs says - at least it seems to, if i not did any major mistake here.

kettanaito commented 1 year ago

Hey, @Wozniaxos. Thanks for reporting this.

Do you have a reproduction repository where I can look into this? I can't help much without a repo I can run and debug.

Wozniaxos commented 1 year ago

@kettanaito thanks for very quick answer!

https://github.com/Wozniaxos/msw-repro here is the repos

npm install and npm run check-test

kettanaito commented 1 year ago

Hey, @Wozniaxos. Thanks for sharing the repository.

Upon the initial run of check-test, the following error was thrown:

 FAIL  src/components/Watchlist/Watchlist.test.tsx [ src/components/Watchlist/Watchlist.test.tsx ]
Invariant Violation:
"fetch" has not been found globally and no fetcher has been configured. To fix this, install a fetch package (like https://www.npmjs.com/package/cross-fetch), instantiate the fetcher, and pass it into your HttpLink constructor. For example:

import fetch from 'cross-fetch';
import { ApolloClient, HttpLink } from '@apollo/client';
const client = new ApolloClient({
  link: new HttpLink({ uri: '/graphql', fetch })
});

This clearly indicates that your testing setup is missing a fetch polyfill. This is something you must add if you wish to test browser code (your app) in Node (Vitest). This has nothing to do with MSW, to be totally clear here.

Adding the polyfill

Just follow the Vitest suggestions:

npm install -D cross-fetch
// packages/project/src/App.tsx
import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client'
+import fetch from 'cross-fetch'

const httpLink = new HttpLink({
  uri: `http://localhost:3000/graphql`,
+ fetch,
})

export const apolloClient = new ApolloClient({
  cache: new InMemoryCache(),
  link: httpLink,
})

Once this is done, let's run the check-test script again:

data is undefined
loading is true
querinasdas dasd asd asd asd asd asd asd asd asd asd g!!

You can see that your console.log statement from the handler is printed, so MSW is functioning as expected, as far as we can tell. But the test still fails on a timeout:

 FAIL  src/components/Watchlist/Watchlist.test.tsx > Watchlist > renders properly
Error: Test timed out in 5000ms.
If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout".

Notice how the timeout in the error message is 5000ms despite you setting it to 10000 on the expect() call in your test:

    await waitFor(() => expect(screen.getByText('Rendered')).toBeDefined(), {
      timeout: 10000,
    })

That's a clear indication that your timeout is not respected in Vitest, and your test fails.

Set the test timeout correctly

Let's set the timeout for your test on the it() block itself:

describe('Watchlist', () => {
  it('renders properly', async () => {
    render(
      <ApolloProvider client={apolloClient}>
        <Watchlist />
      </ApolloProvider>
    )

+   await waitFor(() => expect(screen.getByText('Rendered')).toBeDefined())
+ }, 10_000)
})

Running the test again:

 FAIL  src/components/Watchlist/Watchlist.test.tsx > Watchlist > renders properly
TestingLibraryElementError: Unable to find an element with the text: Rendered. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.

Ignored nodes: comments, script, style
<body>
  <div>
    <div>
      rendered
    </div>
  </div>
</body>

Ignored nodes: comments, script, style
<html>
  <head />
  <body>
    <div>
      <div>
        rendered
      </div>
    </div>
  </body>
</html>

That's some progress! The timeout is gone, and we can see the RTL library saying it's unable to find a node by the text you've provided in the assertion.

Let's compare your expectations (the test) and the actuality (your Watchlist.tsx):

The test

await waitFor(() => expect(screen.getByText('Rendered')).toBeDefined())

Notice the capital "R".

The actuality

// packages/project/src/components/Watchlist/Watchlist.tsx
function Watchlist() {
  const { data, loading } = useWatchlistQuery();

  console.log('data is', data);
  console.log('loading is', loading);

  // Hey, it's a lowercase "r"!
  return data ? <div>rendered</div> : <div/>;
}

Casing matters. Let's account for that:

// packages/project/src/components/Watchlist/Watchlist.test.tsx
+await waitFor(() => expect(screen.getByText('rendered')).toBeDefined())

And running the test again...

data is undefined
loading is true
querinasdas dasd asd asd asd asd asd asd asd asd asd g!!
data is {
  watchlist: [
    {
      __typename: 'WatchlistResponse',
      id: '1',
      venueAccount: 'Bitfinex',
      securityId: '1',
      symbol: 'SHIBUSD'
    },
    {
      __typename: 'WatchlistResponse',
      id: '2',
      venueAccount: 'BitMEX',
      securityId: '2',
      symbol: 'SHIBUSD'
    }
  ]
}
loading is false

 ✓ src/components/Watchlist/Watchlist.test.tsx (1)

Hooray! 🎉 You can clearly see that the mocked query response arrives at your Watchlist.tsx component.

Recap

kettanaito commented 1 year ago

I enjoyed looking into this issue! If you'd like to support my open-source effort, please consider Sponsoring MSW on GitHub. Every contribution counts. It helps me help others without feeling like I'm sacrificing too much of my free time (which I try to do as little as possible, I promise).

Thanks!

Wozniaxos commented 1 year ago

@kettanaito Hey, it was my mistake with that fetch, i was misled cause of rest api handlers worked for me - but we use axios and axios is not using fetch under the hood. I indeed used cross-fetch and mocks are called properly. Speaking about the rest you pointed - i just made quickly repo changes to show you that mock is not called - failing test weren't a problem :) sorry that i didn't specified that and you wasted time for case sentisivness, timeout and other stuff besides that fetch. And BTW, i didn't get error about fetch that you posted

 FAIL  src/components/Watchlist/Watchlist.test.tsx [ src/components/Watchlist/Watchlist.test.tsx ]
Invariant Violation:
"fetch" has not been found globally and no fetcher has been configured. To fix this, install a fetch...

Only warning that fetch is experimental

Maybe because of different node? i use 19.

Thanks again!

kettanaito commented 1 year ago

Oh, got it! I will take a look into this when I have a minute.

MSW doesn't have official support for Node 18 and 19, so if Node version is the culprit, I can't treat this as an issue since we aren't promising compatibility with those versions, to begin with. But it'd be nice to know nonetheless.