ladjs / supertest

🕷 Super-agent driven library for testing node.js HTTP servers using a fluent API. Maintained for @forwardemail, @ladjs, @spamscanner, @breejs, @cabinjs, and @lassjs.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Cannot call Express Next function after throwing Error (TypeScript)

ShinJustinHolly3317 opened this issue · comments

Env:

typescript 4.7.4
Node 14
Supertest 6.2.4
Jest 28.1.3
Express 4.18.1
Express-async-Handler 1.2.0

Test Code

import server, { app } from '/server.ts'

const mockGetCache = jest.fn();
const mockSetCache = jest.fn();

// This is mocking class
jest.mock('../../src/infra/clients/memcached-client', () => jest.fn().mockImplementation(() => ({
  getCache: mockGetCache,
  setCache: mockSetCache,
})));

it('POST cardData Error', async () => {
    mockGetCache.mockImplementation(() => {
      throw new Error('test-error in POST cardData');
    });

    const response = await request(app)
      .post('/api/v1/ad/cardData')
      .send(fakeCardData);

    expect(response.statusCode).toBe(500);
    expect(response.body).toEqual({ code: 9999, message: 'test-error in POST cardData' });
  });

Application code

  1. server.ts
// ...
// set up routes
app.use('/api/v1', routes);

// set up error handler
app.use(errorHandler);
// ....
  1. card data route
// ....
const router = express.Router();

router.post('/', asyncHandler(postCardDataHandler));

export default router;
  1. postCardDataHandler.ts
  try {
    const { cardId } = req.body;
    const { appId } = req.body;

    // init repository
    const cacheClient = new MemcachedClient();
    const memcachedCardRepository = new MemcachedCardRepository(cacheClient);
    const postCardUsecase = new PostCardUsecase(memcachedCardRepository);

    // execute usecase
    const keywords = await postCardUsecase.exec(adId, appId);

    return res.status(200).json(keywordsDto);
  } catch (err) {
    console.error('[some warning]', err)
    return next(err)
  }
  

Simple workflow
My code doesn't respond 500 to client directly. I use express error handler to catch all error and responds error to client here.
So I also use express-async-handler to catch unexpected error, which will call next() and let error-handler catch the error.

Problem:
When I start server, everything works fine with both postman, curl, or my Frontend Website and Mobile.
However it went into Timeout problem when I do integration test with supertest.
Below is log

  ● ad server unit test ›POST cardData Error

    thrown: "Exceeded timeout of 5000 ms for a test.
    Use jest.setTimeout(newTimeout) to increase the timeout value, if this is a long-running test."

Possible reason

next function not working normally.

After digging deep to the express-async-handler, and I found that everything is good with it.
At the final step express-async-handler also call next(err), but my error-handler didn't catch the err.

found similar issue here #529

update

I tried remove express router, and use the handler directly by app.post(...) in server.ts
like

// ...
// set up routes
app.use('/api/v1',(req, res, next) => {
  try {
     //...
  } catch (err) {
     console.log('[handler Error]', err)
     return next(err)
  }

});

// set up error handler
app.use(errorHandler);
// ....

This will work correctly.
Still have no clue why.

anyone can save me?

This issue is because of jest.useFakeTimer will probably mock out some event loop function under next().
Prevent using jest.useFakeTimer will fix this issue.