eugef / node-mocks-http

Mock 'http' objects for testing Express routing functions
Other
747 stars 131 forks source link

Unexpected end of JSON input with async express router #285

Closed cfalch closed 6 months ago

cfalch commented 6 months ago

With express route:

const router = new express.Router();
router
  .route('/test')
  .post(async (req, res) => {
    await Validator.validate(req.body);
    const response = await Controller.control(req.body);
    res.status(200).json(response);
  });

the following jest test fails with SyntaxError: Unexpected end of JSON input at JSON.parse (<anonymous>)

test('broke with single await', async () => {
  const mockControllerResult = { id: '1', status: 'ok' };
  jest.spyOn(Validator, 'validate').mockResolvedValue();
  jest.spyOn(Controller, 'control').mockResolvedValue(mockControllerResult);
  const req = httpMocks.createRequest({
    method: 'POST',
    url: '/test',
    body: { id: '1' },
  });
  const res = httpMocks.createResponse();

  await router(req, res);

  expect(res.statusCode).toEqual(200);
  const body = res._getJSONData();
  expect(body).toEqual(mockControllerResult);
});

At the point I'm parsing JSONData, res has statusCode 200, statusMessage 'OK' but _isEndCalled() is false. I've found a very unusual fix, 2 awaits:

await await router(req, res);

Am I doing something wrong within the context of the framework, or is this a bug?

eugef commented 6 months ago

Hi @cfalch, seems like something is wrong with your code.

Check what is retuned by res._getData().

cfalch commented 6 months ago

res._getData() is an empty string. Do you see anything wrong with the code? This is basically the entire example - it doesn't matter what Validator or Controller do because the jest spys are mocking responses. res.getData() should be the value of mockControllerResult -> "{ id: '1', status: 'ok' }". If I add a 2nd await as I described, _getData() does then return "{ id: '1', status: 'ok' }"

eugef commented 6 months ago

I think the problem might be that you're trying to test the entire route, but this library is mainly meant for testing the route handler.

Try the following:

const router = new express.Router();

const routeHandler = async (req, res) => {
    await Validator.validate(req.body);
    const response = await Controller.control(req.body);
    res.status(200).json(response);
}

router
  .route('/test')
  .post(routeHandler);

And in the test change await router(req, res); to await routeHandler(req, res);

cfalch commented 6 months ago

@eugef I appreciate you spending the time to help sort this out. I understand your suggestion which does test out, and will work on refactoring per this example.