Jest: "globalTeardown" and "globalSetup" process behavior not as expected

Created on 30 Jan 2018  路  6Comments  路  Source: facebook/jest

Do you want to request a _feature_ or report a _bug_?
Somewhere between feature enhancement and a bug.

What is the current behavior?
Scenario A. You pass a globalSetup script in your config the result is:

  • Tests run fine
  • Watch works
  • Regular test run never "finishes"/process never exits (an issue with things that automatically run tests)

Scenario B. You pass a globalSetup and globalTeardown scripts, with the teardown running a process.exit() when it 's done, the result is:

  • Test run fine
  • Watch kills itself
  • Regular test run finishes if you manually process.exit()

Scenario C. You pass a globalSetup and globalTeardown scripts, with the teardown _not_ running a process.exit() when it 's done - resulting in the same behavior as Scenario A.

If the current behavior is a bug, please provide the steps to reproduce and
either a repl.it demo through https://repl.it/languages/jest or a minimal
repository on GitHub that we can yarn install and yarn test.

Believe you can reproduce with any globalSetup/Teardown scripts - mine are async - here they are for reference:
globalSetup
```[javascript]
const Sequelize = require('sequelize');
const Umzug = require('umzug');
const path = require('path');
const env = 'test';
const config = require('./src/dbConfig')[env];
const sequelize = new Sequelize(config);

const create = async () => {
const createConfig = { ...config };

delete createConfig.database;

const createSequelize = new Sequelize(createConfig);

await createSequelize.query(CREATE DATABASE ${createSequelize.getQueryInterface().quoteIdentifier(config.database)}, {
type: createSequelize.QueryTypes.RAW
}).catch(e => console.error(e));
};

const migrate = async () => {
const umzug = new Umzug({
storage: 'sequelize',
storageOptions: {
sequelize
},
migrations: {
params: [
sequelize.getQueryInterface(),
Sequelize
],
path: path.join(__dirname, './src/migrations')
}
});

await umzug.up();
};

const seed = async () => {
const umzug = new Umzug({
storage: 'sequelize',
storageOptions: {
sequelize
},
migrations: {
params: [
sequelize.getQueryInterface(),
Sequelize
],
path: path.join(__dirname, './src/testSeeders')
}
});

await umzug.up();
};

module.exports = async () => {
await create()
.then(() => migrate()
.then(() => seed())
);
};

`globalTeardown`
```[javascript]
const Sequelize = require('sequelize');
const env = 'test';
const config = require('./src/dbConfig')[env];

const drop = async () => {
  const dropConfig = { ...config };

  delete dropConfig.database;

  const dropSequelize = new Sequelize(dropConfig);

  await dropSequelize.query(`DROP DATABASE ${dropSequelize.getQueryInterface().quoteIdentifier(config.database)}`, {
    type: dropSequelize.QueryTypes.RAW
  }).catch(e => console.error(e));
};


module.exports = async () => {
  await drop()
    .then(() => process.exit());
};

What is the expected behavior?

Something either passed to teardown to let users of this awesome config/functionality to be able to safely exit tests or any other proposal that rectifies behavior. Or I'm an idiot and there are already things to hook into to correct this behavior - just haven't been able to find anything after scouring the internets.

Please provide your exact Jest configuration and mention your Jest, node,
yarn/npm version and operating system.

  "jest": {
    "moduleDirectories": [
      "node_modules",
      "src"
    ],
    "moduleNameMapper": {
      "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js",
      "\\.(css|less)$": "identity-obj-proxy"
    },
    "testEnvironment": "jsdom",
    "snapshotSerializers": [
      "enzyme-to-json/serializer",
      "jest-glamor-react"
    ],
    "collectCoverageFrom": [
      "src/**/*.js",
      "!src/*.js",
      "!src/state/_stubs/*",
      "!src/models/*",
      "!src/seeders/*",
      "!src/testSeeders/*",
      "!src/migrations/*"
    ],
    "coverageReporters": [
      "json-summary",
      "lcov"
    ],
    "coverageThreshold": {
      "global": {
        "lines": 10
      }
    },
    "setupTestFrameworkScriptFile": "./testSetup.js",
    "globalSetup": "./testGlobalSetup.js",
    "globalTeardown": "./testGlobalTeardown.js"
  },

[email protected]
[email protected]
[email protected]
[email protected]

Most helpful comment

You can use --forceExit in order to not allow Jest to wait for handles keeping the process open.

Normal errors keeping the process open are either setTimeout or setInterval without an .unref() call (if using node), or some server/db-connection not closed. I'm not sure what's the best way to figure out what's keeping the process open, ideas would be great! why-is-node-running used to work pretty good, but not in later versions of node.

If you find some tool capable of tracking down the open handle, please share it. Maybe we could link to it from the docs

All 6 comments

I'm curious what else did you expect by running process.exit()? The hook is to tear down your APIs (e.g. Sequelize here), not to shut the whole process down.

@thymikee I expect it to close the test process which would normally happen on a test run, but doesn't seem to with a globalTeardown script defined. As you said I "tear down [my] APIs" but on say a normal test run which can be fired by CI I need the test to exit and not just sit spinning like it would otherwise. If I change that script to simply Promise.resolve instead of process.exit() the test process will never exit on a test run. At this point I'm not certain how to get to the point where jest actually considers my tests "done" as it normally would and watch/run with normal behavior while using globalSetup and globalTeardown, and have seen no examples or documentation for these options to guide me in doing such.

You can use --forceExit in order to not allow Jest to wait for handles keeping the process open.

Normal errors keeping the process open are either setTimeout or setInterval without an .unref() call (if using node), or some server/db-connection not closed. I'm not sure what's the best way to figure out what's keeping the process open, ideas would be great! why-is-node-running used to work pretty good, but not in later versions of node.

If you find some tool capable of tracking down the open handle, please share it. Maybe we could link to it from the docs

Thanks so much for the direction @SimenB ! With your guidance I dug around and found a project named wtfnode that worked like a charm with 8.7.0 and I did indeed have some errant connection pools that I was assuming were being closed. Was simple to use with setup/teardown:

const wtf = require('wtfnode');
const Sequelize = require('sequelize');
const Umzug = require('umzug');
const path = require('path');
const env = 'test';
const config = require('./src/dbConfig')[env];
const sequelize = new Sequelize(config);

const create = async () => {
  const createConfig = { ...config };

  delete createConfig.database;

  const createSequelize = new Sequelize(createConfig);

  await createSequelize.query(`CREATE DATABASE ${createSequelize.getQueryInterface().quoteIdentifier(config.database)}`, {
    type: createSequelize.QueryTypes.RAW
  })
    .catch(e => console.error(e))
    .then(() => createSequelize.close())
    .catch(e => console.error(e));
};

const migrate = async () => {
  const umzug = new Umzug({
    storage: 'sequelize',
    storageOptions: {
      sequelize
    },
    migrations: {
      params: [
        sequelize.getQueryInterface(),
        Sequelize
      ],
      path: path.join(__dirname, './src/migrations')
    }
  });

  await umzug.up();
};

const seed = async () => {
  const umzug = new Umzug({
    storage: 'sequelize',
    storageOptions: {
      sequelize
    },
    migrations: {
      params: [
        sequelize.getQueryInterface(),
        Sequelize
      ],
      path: path.join(__dirname, './src/testSeeders')
    }
  });

  await umzug.up();
};

module.exports = async () => {
  global.wtf = wtf;
  global.sequelize = sequelize;
  await create()
    .then(() => migrate()
      .then(() => seed())
    );
};

then in teardown:

const Sequelize = require('sequelize');
const env = 'test';
const config = require('./src/dbConfig')[env];

const drop = async () => {
  const dropConfig = { ...config };

  delete dropConfig.database;

  const dropSequelize = new Sequelize(dropConfig);

  await dropSequelize.query(`DROP DATABASE ${dropSequelize.getQueryInterface().quoteIdentifier(config.database)}`, {
    type: dropSequelize.QueryTypes.RAW
  })
    .catch(e => console.error(e))
    .then(() => dropSequelize.close())
    .catch(e => console.error(e));
};


module.exports = async () => {
  // wtf.dump() somewhere in here to debug
  await drop()
    .then(() => {
      sequelize.close();
    });
};

Link to repo: https://github.com/myndzi/wtfnode

@reidblomquist great! Mind sending a PR to the docs linking to that utility?

@SimenB Will do - might not get to it today but will get a PR up as soon as I can

Was this page helpful?
0 / 5 - 0 ratings