mswjs / msw

Industry standard API mocking for JavaScript.
https://mswjs.io
MIT License
15.83k stars 514 forks source link

Mock response is overwritten with {} #1056

Closed MikePolen closed 2 years ago

MikePolen commented 2 years ago

Environment

Name Version
msw 0.36.4
node v16.13.0
OS MAC 12.0.1

Request handlers

setup:
// Establish API mocking before all tests.
beforeAll(() => server.listen({
    onUnhandledRequest: 'warn',
  }))
// Reset any request handlers that we may add during the tests,
// so they don't affect other tests.
afterEach(() => {
    server.resetHandlers();

  // This is the solution to clear RTK Query cache after each test
    store.dispatch(orchestrator.util.resetApiState());
});
// Clean up after the tests are finished.
afterAll(() => server.close())

handler:
import { rest } from 'msw'

export const handlers = [
    rest.post('http://localhost/cash-advance/validate', (req, res, ctx) => {
        return res(
            ctx.status(200),
            ctx.json({"linkId":"123","validationState":"VALID_CUSTOMER","customer":{}}),
        )
    })
  ];

Actual request

I am using RTK Query

// Example of making a request. Provide your code here.
  const {
    data,
    isError,
    error,
    refetch: refetchPostInitialize,
  } = usePostValidateQuery(linkId, {
    skip: linkId == null || idToken === '',
  });
Here is the createAPI:
export const orchestrator = createApi({
  reducerPath: 'orchestrator',
  baseQuery,
  endpoints: (builder) => ({
    postValidate: builder.query({
      query: (linkId: string | undefined) => ({
        url: `cash-advance/validate?linkId=${linkId}`,
        method: 'POST',
      }),
    }),

Current behavior

When I run the test it appears to return nothing. when I run the test using the debug package with DEBUG=* I see the following:

  XHR POST cash-advance/validate?linkId=link_id received mocked response {
  status: 200,
  statusText: 'OK',
  headers: { 'x-powered-by': 'msw', 'content-type': 'application/json' },
  body: '{"linkId":"123","validationState":"VALID_CUSTOMER","customer":{}}'
} +149ms
  XHR POST cash-advance/validate?linkId=link_id trigger "loadstart" (1) +0ms
  XHR POST cash-advance/validate?linkId=link_id resolve listener for event "loadstart" +0ms
  XHR POST cash-advance/validate?linkId=link_id set response status 200 OK +0ms
  XHR POST cash-advance/validate?linkId=link_id set response headers HeadersPolyfill {
  _headers: { 'x-powered-by': 'msw', 'content-type': 'application/json' },
  _names: Map(2) {
    'x-powered-by' => 'x-powered-by',
    'content-type' => 'content-type'
  }
} +0ms
  XHR POST cash-advance/validate?linkId=link_id readyState change 1 -> 2 +0ms
  XHR POST cash-advance/validate?linkId=link_id triggerring readystate change... +0ms
  XHR POST cash-advance/validate?linkId=link_id trigger "readystatechange" (2) +0ms
  XHR POST cash-advance/validate?linkId=link_id resolve listener for event "readystatechange" +0ms
  XHR POST cash-advance/validate?linkId=link_id response type blob +1ms
  XHR POST cash-advance/validate?linkId=link_id coerced response body to {"linkId":"123","validationState":"VALID_CUSTOMER","customer":{}} +0ms
  XHR POST cash-advance/validate?linkId=link_id get response header "content-type" +0ms
  XHR POST cash-advance/validate?linkId=link_id resolved response header "content-type" to "application/json" HeadersPolyfill {
  _headers: { 'x-powered-by': 'msw', 'content-type': 'application/json' },
  _names: Map(2) {
    'x-powered-by' => 'x-powered-by',
    'content-type' => 'content-type'
  }
} +0ms
  XHR POST cash-advance/validate?linkId=link_id resolving response body as Blob { type: 'application/json' } +0ms
  XHR POST cash-advance/validate?linkId=link_id get response header "Content-Type" +0ms
  XHR POST cash-advance/validate?linkId=link_id resolved response header "Content-Type" to "application/json" HeadersPolyfill {
  _headers: { 'x-powered-by': 'msw', 'content-type': 'application/json' },
  _names: Map(2) {
    'x-powered-by' => 'x-powered-by',
    'content-type' => 'content-type'
  }
} +1ms
  XHR POST cash-advance/validate?linkId=link_id set response body Blob {} +0ms
  XHR POST cash-advance/validate?linkId=link_id readyState change 2 -> 3 +0ms
  XHR POST cash-advance/validate?linkId=link_id triggerring readystate change... +0ms
  XHR POST cash-advance/validate?linkId=link_id trigger "readystatechange" (3) +0ms
  XHR POST cash-advance/validate?linkId=link_id resolve listener for event "readystatechange" +0ms
  XHR POST cash-advance/validate?linkId=link_id trigger "progress" (3) +0ms
  XHR POST cash-advance/validate?linkId=link_id resolve listener for event "progress" +0ms
  XHR POST cash-advance/validate?linkId=link_id readyState change 3 -> 4 +0ms
  XHR POST cash-advance/validate?linkId=link_id triggerring readystate change... +0ms
  XHR POST cash-advance/validate?linkId=link_id trigger "readystatechange" (4) +0ms
  XHR POST cash-advance/validate?linkId=link_id resolve listener for event "readystatechange" +0ms
  XHR POST cash-advance/validate?linkId=link_id trigger "load" (4) +0ms
  XHR POST cash-advance/validate?linkId=link_id resolve listener for event "load" +0ms
  XHR POST cash-advance/validate?linkId=link_id get all response headers +1ms
  XHR POST cash-advance/validate?linkId=link_id trigger "loadend" (4) +0ms
  XHR POST cash-advance/validate?linkId=link_id resolve listener for event "loadend" +0ms

At the beginning I see the mock response. Then further down I see "response type blob" then "set response body Blob {}". The UI gets nothing in the data variable. It is undefined which would correspond to the api returning {}.

Expected behavior

I expect the data variable to get

{"linkId":"123","validationState":"VALID_CUSTOMER","customer":{}}

Screenshots

kettanaito commented 2 years ago

Hey, @MikePolen! Thanks for reporting this.

This issue has a chance of being already fixed. Let us update to the latest @mswjs/interceptors and test it out.

github-actions[bot] commented 2 years ago

:tada: This issue has been resolved in version 0.38.0 :tada:

The release is available on:

Your semantic-release bot :package::rocket:

edwinvrgs commented 2 years ago

Hey, I'm facing this same issue in version 0.42.1.

My problem is that I have a handler like this:

       graphql.query<CurrentUserQuery>(
                    "currentUser",
                    (_req, res, ctx) => {
                        return res(
                            ctx.data({
                                currentUser: user,
                            }),
                        )
                    },
                ),

Where user is a mocked object. I also have this to inspect the query.

server.events.on("response:mocked", (args) => {
            console.log({ args })
        })

This is what I receive from that event:

    {
      args: {
        status: 200,
        statusText: 'OK',
        headers: HeadersPolyfill { headers: [Object], names: [Map] },
        body: '{"data":{"currentUser":{"id":"16bgr10bw0","firstName":"Halie","lastName":"Mollie","email":"Arlo_Sipes@hotmail.com","jobFunction":"Other"}}}'
      }
    }

And this is what I receive as response from the query:

    const res = await apolloClient.query({
            query: CurrentUserDocument,
        })
    console.log(res.data)
{ currentUser: {} }

I'm using @apollo/client - 3.6.5.

edwinvrgs commented 2 years ago

Fixed by using __typename.

  ctx.data({
    currentUser: {
      __typename: "User",
      ...user
    },
  }),
kettanaito commented 2 years ago

@edwinvrgs, we're mentioning the request integrity with the GraphQL client here. It may not be obvious at first but you need to compose the same response in regards to what the client expects. In the case of Apollo, all fields must have __typename. Apollo will strip off any fields that don't. This is a behavior not specific to MSW in any way.

edwinvrgs commented 2 years ago

@edwinvrgs, we're mentioning the request integrity with the GraphQL client here. It may not be obvious at first but you need to compose the same response in regards to what the client expects. In the case of Apollo, all fields must have __typename. Apollo will strip off any fields that don't. This is a behavior not specific to MSW in any way.

Yeah, at the beginning I thought it was related to MSW but I was wrong. Thanks for your comment.