Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jest global teardown runs before tests finish?

I'm trying to make sure my app gets properly destroyed after all my Jest tests have run, but I'm running into some very strange behaviour trying to use Jest's global teardown config value.

Here's the situation: my app creates a database connection. It also has a destroy method that closes the database connection. This works.

I have a single test that starts the server, runs a query against the database connection. In my global teardown function, I call app.destroy(), but the process hangs.

If I comment out the destroy call in the global teardown function and put app.destroy() in my test after the query, Jest doesn't hang and closes like it's supposed to. I can also put afterAll(() => app.destroy()) in my test file and things work properly.

Here is my jest.config.js

module.exports = {
  testEnvironment: 'node',
  roots: [
    '<rootDir>/src'
  ],
  transform: {
    '^.+\\.tsx?$': 'ts-jest'
  },
  testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$',
  moduleFileExtensions: [
    'ts',
    'tsx',
    'js',
    'jsx',
    'json',
    'node'
  ],
  globalSetup: '<rootDir>/src/testSetup.ts',
  globalTeardown: '<rootDir>/src/testTeardown.ts'
};

Here is the test (it's currently the only test in the app):

import app from '../..';

describe('User router', () => {
  it('Should respond with an array of user objects', async () => {
    await app.models.User.query();
  });
});

And here is the global teardown in <rootDir>/src/testTeardown.ts:

import app from './index';

module.exports = async function testTeardown() {
  await app.destroy();
};

Using the code above, the process hangs after tests finish. I've tried adding a console.log to testTeardown and the end of the test, and the logs happen in the correct order: test logs, then teardown logs. However if I move app.destroy() into my test it works perfectly:

import app from '../..';

describe('User router', () => {
  it('Should respond with an array of user objects', async () => {
    await app.models.User.query();
    await app.destroy();
  });
});

This also works:

import app from '../..';

afterAll(() => app.destroy());

describe('User router', () => {
  it('Should respond with an array of user objects', async () => {
    await app.models.User.query();
  });
});

Why is this happening?

Also just for shits and giggles I tried setting a global._app in the test and then checking it in the teardown handler, but it was undefined. Do Jest's setup/teardown functions even get run in the same process as the tests?

like image 577
SimpleJ Avatar asked Feb 01 '19 22:02

SimpleJ


1 Answers

No, jest globalSetup and globalTeardown files don't necessarily get run in the same process as your tests. This is because jest parallelises your tests and runs each test file in a separate process, but there is only one global setup/teardown phase for the combined set of test files.

You can use setupFiles to add a file that gets run in process with each test file. In the setupFiles file you can put:

afterAll(() => app.destroy());

Your jest config is just

module.exports = {
  testEnvironment: 'node',
  roots: [
    '<rootDir>/src'
  ],
  transform: {
    '^.+\\.tsx?$': 'ts-jest'
  },
  testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$',
  moduleFileExtensions: [
    'ts',
    'tsx',
    'js',
    'jsx',
    'json',
    'node'
  ],
  setupFiles: ['<rootDir>/src/testSetup.ts']
};
like image 194
ForbesLindesay Avatar answered Sep 29 '22 13:09

ForbesLindesay