Msw: Question: How do I mock an empty response?

Created on 2 Dec 2020  ยท  15Comments  ยท  Source: mswjs/msw

Today is the first time I am using this library. I am trying to mock an API for unit-testing using React Testing Library with CRA in TypeScript. The API takes a PUT request and gives 204 success response with empty payload in the response. So response looks like this in Chrome:

Screenshot 2020-12-02 at 9 37 46 PM

I tried this:

rest.put('/api', (req: MockedRequest<DefaultRequestBodyType, RequestParams>, res: ResponseComposition<any>, ctx: RestContext) => res(ctx.status(204))),

but got this error:

TypeError: Cannot read property 'length' of null
at node_modules/node-request-interceptor/src/interceptors/XMLHttpRequest/XMLHttpRequestOverride.ts:280:37

Apologies if its a silly question to ask. Also apologies if I am posting in wrong format, I have never posted a query before.

bug potentially solved node

All 15 comments

Hey, @indiansage. Thanks for reaching out.

Your request handler appears to be correct. Could you double check the version of node-request-interceptor installed?

$ npm ls node-request-interceptor

If it's _lower_ than 0.5.6, please re-install your dependencies (npm install msw should suffice). Let me know how it goes.

For what it's worth, I was also taking a look at this and I tried to reproduce the issue with a test within test repo but I wasn't able to.

There was an issue in node-request-interceptor that resulted in this exception. The issue has been fixed in 0.5.6, so updating the version should resolve the issue.

I checked the version, it is 0.5.6. Other versions I am using are [email protected], [email protected] if that helps. Tried to make a sandbox, but CodeSandbox uses browser environment for running tests so couldn't replicate exactly. I got a workaround for now to make my test pass, by using
res(ctx.status(204), ctx.json({}))
However this is not the correct way I think.

Could you run your testing command with the DEBUG=* prefix?

# For example
$ DEBUG=* yarn test 

Please post the output here.

I have tried to create an example but I was no fortunate

I cannot post all logs due to confidentiality reasons at work, so I have put the last lines of logs here, replacing api names and payloads with placeholders. I think my api calls are leaking onto each other, since logs are showing responses for previous api calls made in the same test as the response for the PUT call. I have used

  beforeAll(() => server.listen());
  afterEach(() => server.resetHandlers());
  afterAll(() => server.close());

at the start of the test suite. Used handlers:

const handlers = [
  rest.get('/api', (_req: MockedRequest<DefaultRequestBodyType, RequestParams>, res: ResponseComposition<any>, ctx: RestContext) =>
    res(ctx.status(200), ctx.json(response)),
  ),
  rest.get('/prev-api-2, (_req: MockedRequest<DefaultRequestBodyType, RequestParams>, res: ResponseComposition<any>, ctx: RestContext) =>
    res(ctx.status(200), ctx.json(response2)),
  ),
  rest.put('/api', (_req: MockedRequest<DefaultRequestBodyType, RequestParams>, res: ResponseComposition<any>, ctx: RestContext) => res(ctx.status(204))),
];

Server is as
export const server = setupServer(...handlers);

These are the debug logs(modified but I think you get the drift):

2020-12-03T02:05:24.612Z XHR GET /previous-api trigger progress
2020-12-03T02:05:24.612Z XHR GET /previous-api trigger loadstart
2020-12-03T02:05:24.612Z XHR GET /previous-api get all response headers
2020-12-03T02:05:24.612Z XHR GET /previous-api trigger load
2020-12-03T02:05:24.612Z XHR GET /previous-api trigger loadend
2020-12-03T02:05:24.642Z XHR GET /previous-api-2 open {
  method: 'GET',
  url: '/previous-api-2',
  async: true,
  user: undefined,
  password: undefined
}
2020-12-03T02:05:24.642Z XHR GET /previous-api-2 reset
2020-12-03T02:05:24.644Z XHR GET /previous-api-2 set request header Accept application/json, text/plain, */*
2020-12-03T02:05:24.644Z XHR GET /previous-api-2 send GET /previous-api-2
2020-12-03T02:05:24.645Z XHR GET /previous-api-2 request headers { accept: 'application/json, text/plain, */*' }
2020-12-03T02:05:24.645Z XHR GET /previous-api-2 awaiting mocked response...
2020-12-03T02:05:24.645Z RequestInterceptor applying middleware... {
  url: URL {},
  method: 'GET',
  body: '',
  headers: { accept: 'application/json, text/plain, */*' }
}
2020-12-03T02:05:24.701Z XHR PUT /api open {
  method: 'PUT',
  url: '/api',
  async: true,
  user: undefined,
  password: undefined
}
2020-12-03T02:05:24.701Z XHR PUT /api reset
2020-12-03T02:05:24.702Z XHR PUT /api set request header Accept application/json, text/plain, */*
2020-12-03T02:05:24.702Z XHR PUT /api set request header Content-Type application/json
2020-12-03T02:05:24.702Z XHR PUT /api send PUT /api
2020-12-03T02:05:24.702Z XHR PUT /api request headers {
  accept: 'application/json, text/plain, */*',
  'content-type': 'application/json'
}
2020-12-03T02:05:24.702Z XHR PUT /api awaiting mocked response...
2020-12-03T02:05:24.702Z RequestInterceptor applying middleware... {
  url: URL {},
  method: 'PUT',
  body: **[ ... request body ... ]**,
  headers: {
    accept: 'application/json, text/plain, */*',
    'content-type': 'application/json'
  }
}
2020-12-03T02:05:24.707Z XHR PUT /api received mocked response {
  status: 200, **(This status code is from previous response)**
  statusText: 'OK',
  headers: { 'x-powered-by': 'msw', 'content-type': 'application/json' },
  body: **[ ... previous api response coming here ... ]**
}
2020-12-03T02:05:24.707Z XHR PUT /api assigned response status 200 OK
2020-12-03T02:05:24.707Z XHR PUT /api assigned response headers { 'x-powered-by': 'msw', 'content-type': 'application/json' }
2020-12-03T02:05:24.707Z XHR PUT /api response type json
2020-12-03T02:05:24.707Z XHR PUT /api coerced response body to **[ ... previous api response coming here ... ]**
2020-12-03T02:05:24.707Z XHR PUT /api resolving response body as JSON
2020-12-03T02:05:24.708Z XHR PUT /api assigned response body **[ ... previous api response coming here ... ]**
2020-12-03T02:05:24.708Z XHR PUT /api trigger progress
2020-12-03T02:05:24.708Z XHR PUT /api trigger loadstart
2020-12-03T02:05:24.708Z XHR PUT /api get all response headers
2020-12-03T02:05:24.708Z XHR PUT /api trigger load
2020-12-03T02:05:24.708Z XHR PUT /api trigger loadend
2020-12-03T02:05:24.722Z XHR PUT /api received mocked response {
  status: 204,
  statusText: 'No Content',
  headers: { 'x-powered-by': 'msw' },
  body: null
}
2020-12-03T02:05:24.722Z XHR PUT /api assigned response status 204 No Content
2020-12-03T02:05:24.722Z XHR PUT /api assigned response headers { 'x-powered-by': 'msw' }
2020-12-03T02:05:24.722Z XHR PUT /api response type json
2020-12-03T02:05:24.722Z XHR PUT /api coerced response body to 
2020-12-03T02:05:24.722Z XHR PUT /api resolving response body as JSON
2020-12-03T02:05:24.723Z XHR PUT /api assigned response body null
2020-12-03T02:05:25.743Z RequestInterceptor restore
2020-12-03T02:05:25.743Z http override restoring patches...
2020-12-03T02:05:25.743Z XHR restoring patches...
 FAIL  src/.../index.test.tsx
  โ— My unit test

    TypeError: Cannot read property 'length' of null

      at node_modules/node-request-interceptor/src/interceptors/XMLHttpRequest/XMLHttpRequestOverride.ts:280:37

I understand the problem here in getResponseBody function of node-request-interceptor if the body is not defined and the responseType is json it will be parsed to causing the issue because json.parse("") will throw an exception and in the catch it will return null.

I think that the problem is in this.responseType because it seams that is never updated.

Thank you for providing the logs, @indiansage.

The issue is with the_request body: for some reason XHR has responseType as "json". Somewhere along with the request making chain you, or your request client, sets the response type as JSON, although the actual response body is null (empty body).

The issue, however, is also because node-request-interceptor doesn't handle this scenario as a regular XMLHttpRequest does: gracefully fallbacks to response body of null. I've issued a pull request to fix that, as well as refine when and how the "progress" event is called.

@marcosvega91, partially, yes. We are using a custom parseJson function that gracefully handles invalid payload (returns null without raising an exception).

Do you suspect a responseType value persistency across unrelated requests?

Yes I think that before that request there is a not handled request that has a responseType equal to json that will persist in the next request

btw I don't if is correct that parseJson returns a null in that case. It will throw an exception then when response.length is evaluated. Maybe is better to print some message and return an empty string.

@marcosvega91, I've addressed that in the fix pull request. There are two separate statements:

  • Response JSON body parsing must be graceful. XHR doesn't raise an exception if the response body is an invalid JSON. We should retain that behavior.
  • The "progress" event, where we reference this.response.length was implemented incorrectly. Instead, I suggest we convert any existing mocked response body (basically, a string) into a Buffer and access its length.

Yes I think that before that request there is a not handled request that has a responseType equal to json that will persist in the next request

This would be truly peculiar, as responseType value is received from the request client, we are not setting it anywhere in the XMLHttpRequestOverride class.

Published the update in [email protected]. Re-installing the dependencies should propagate the fix to your project.

It worked, thanks for the quick resolution @kettanaito!

Was this page helpful?
0 / 5 - 0 ratings