Jest: Make "globalSetup" and "globalTeardown" work together with "transform"

Created on 23 Dec 2017  路  38Comments  路  Source: facebook/jest

Do you want to request a _feature_ or report a _bug_?
Feature

What is the current behavior?
Modules defined in globalSetup and globalTeardown are not being transformed as defined in transform configuration entry.

What is the expected behavior?
Modules should be transformed.

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

"jest": {
        "globalSetup": "<rootDir>/test/setup.ts",
        "globalTeardown": "<rootDir>/test/teardown.ts",
        "collectCoverage": true,
        "mapCoverage": true,
        "transform": {
            "^.+\\.ts?$": "ts-jest"
        },
        "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(js|ts)$",
        "moduleFileExtensions": [
            "ts",
            "js",
            "json",
            "node"
        ]
    }

jest 22.0.4
ts-jest 22.0.0
node 8.9.2
npm 5.5.1
OS Windows 10

Feature Request Help Wanted

Most helpful comment

If anyone runs into this, you can trivially work around by performing these two steps.

  1. Specify in jest.json:
"globalSetup": "<rootDir>/test_setup/global_setup_hook.js"
  1. Create a .js file 'global_setup_hook.js' and invoke your typescript code from there:
require("ts-node/register");

// If you want to reference other typescript modules, do it via require:
const my_typescript_module = require('my_typescript_module');

module.exports = async function () {
  // Call your initialization methods here.
  await my_typescript_module.Initialize();
  return null;
};

This assumes that you have "ts-node": "^5.0.1" in your package.json. No changes required in jest or ts-jest for this to work.

All 38 comments

I'm not sure we can do this, since the whole point of global{Setup,Teardown} is that it runs before we create a runtime (which is what contains our require which passes stuff through the transforms).

@cpojer thoughts on this?

I think we should transform it if we can, but I鈥檓 not sure right now what code changes that would entail. It may make sense to split the transformer into a separate package, something I wanted to do for a while.

Is there a workaround here? I'd like to be able to load some support code in globalSetup that uses ES6 modules and I'd prefer to not have to rewrite all of it back to requires/exports.

No current workaround that I know of, sorry. PR welcome here, though 馃檪

@glasser I'm currently working around this issue by using babel-register and babel-pollyfill at the top of my jest.config.js.

yarn add babel-register babel-pollyfil
const { readFileSync } = require('fs')
const babelConfig = JSON.parse(readFileSync('./.babelrc', 'utf8'))

require('babel-register')(babelConfig)
require('babel-polyfill')

const { join } = require('path')

const ROOT = process.cwd()
// const TESTS = join(ROOT, '__tests__')
const JEST_ENV = join(ROOT, '__jest_env__')

module.exports = {
  verbose: true,
  transform: {
    '^.+\\.jsx?$': 'babel-jest'
  },
  globalSetup: join(JEST_ENV, 'setup.js'),
  globalTeardown: join(JEST_ENV, 'teardown.js'),
  testEnvironment: join(JEST_ENV, 'server-environment.js')
}

@opasno You seem to be using typescript, so try this method. In my case, I could easily solve the problem. I do not know if this method is useful in all cases, but I did the following things.

  • write setup/teardown code by pure js that is compatible with your runtime even if you are using another altjs language.
  • require suitable register code that you are using (e.g. like babel/register, ts-node/register

code will be like this:
__tests__/globalSetup.js, db/index.ts

require("ts-node/register");
const {db} = require("../db");
module.exports = async function () {
  return db.setup();
};

Note that entire setup/teardown codes will be executed in non-jest environment, so all modules and objects related to Jest can not be referred while setup/teardown. However, It is enough to do setup/teardown.

Is anybody working on a PR for this already? Just encountered this issue and took me awhile to realise what was happening and track down this issue. At the very least, maybe the docs could be updated to indicate that transforms will not take place on globalSetup due to it being outside a jest runtime.

If anyone runs into this, you can trivially work around by performing these two steps.

  1. Specify in jest.json:
"globalSetup": "<rootDir>/test_setup/global_setup_hook.js"
  1. Create a .js file 'global_setup_hook.js' and invoke your typescript code from there:
require("ts-node/register");

// If you want to reference other typescript modules, do it via require:
const my_typescript_module = require('my_typescript_module');

module.exports = async function () {
  // Call your initialization methods here.
  await my_typescript_module.Initialize();
  return null;
};

This assumes that you have "ts-node": "^5.0.1" in your package.json. No changes required in jest or ts-jest for this to work.

If anyone is working on a PR for this, keep in mind that testEnvironment files are not transpiled either.

Is this the same with setupTestFrameworkScriptFile? It is not transpiled?

@tachang Most likely. It would be extremely easy to test, why don't you do that yourself?

@WillAvudim your solution works greatly :) I have shortened it to a two-liner file:

setup_hook.js:

require("ts-node/register");
module.exports = require('./setup').default;

This works, because I have the setup file declared with default export function:

setup.ts:

import { anything } from 'you-want';

export default async function() {
  // do all the setup you want in TypeScript
}

I hope someone finds this useful.

@tachang setupTestFrameworkScriptFile should work as it's required through the runtime: https://github.com/facebook/jest/blob/b60f44a9829b51bc4bc387dbef8d7d37de3cc7e6/packages/jest-jasmine2/src/index.js#L129-L131 & https://github.com/facebook/jest/blob/b60f44a9829b51bc4bc387dbef8d7d37de3cc7e6/packages/jest-circus/src/legacy_code_todo_rewrite/jest_adapter.js#L76-L78


I'm still not sure how to solve this. One thing is to transpile the setup file itself (that can be done pretty easily), but to also transpile files required from it forces us to implement require or hook into node's own require. The first is difficult since it's part of jest-runtime which is not created yet. The second since we should use something like https://www.npmjs.com/package/pirates (which is what babel-register uses) which feels... invasive. Might be the correct solution though.

Anybody wanna create a PoC using https://www.npmjs.com/package/pirates?

We could also print a pretty error if we get a syntax error saying we don't transform. That's probably better than what we have now

Should we always bail if there's a syntax error in the global setup, or should we respect bail flag in the config?

would love to see this. I am using both babel and typescript, and not being able to use es6 for some global setup in test files in a centralized manner kind of sucks. is another workaround just importing the function in the tests you need as well? not as convenient but it would be transpiled right?

@SimenB What is the approach we decided to go with?

I amended @WillAvudim example to support tsconfig-paths

yarn add --dev tsconfig-paths

require("ts-node/register");
require('tsconfig-paths/register');

// If you want to reference other typescript modules, do it via require:
const my_typescript_module = require('my_typescript_module');

module.exports = async function () {
  // Call your initialization methods here.
  await my_typescript_module.Initialize();
  return null;
};

I don't use typescript, is there another solution?

@shai32, you can do the same with @babel/register and @babel/polyfill:

require('@babel/register');
require('@babel/polyfill');
module.exports = require('./setup').default;

@ezze
I tried that, but I am using yarn workspace, and it doesn't work with yarn workspace (shared packages are not compiled because they are in node_moduels)

I don't understand why setupTestFrameworkScriptFile is transformed but globalSetup is not.

This is available in Jest 24

Are https://jestjs.io/docs/en/configuration.html#modulenamemapper-object-string-string concidered if using global setup with typescript

No, jest does not control the resolution algorithm in this case

@glasser I'm currently working around this issue by using babel-register and babel-pollyfill at the top of my jest.config.js.

yarn add babel-register babel-pollyfil
const { readFileSync } = require('fs')
const babelConfig = JSON.parse(readFileSync('./.babelrc', 'utf8'))

require('babel-register')(babelConfig)
require('babel-polyfill')

const { join } = require('path')

const ROOT = process.cwd()
// const TESTS = join(ROOT, '__tests__')
const JEST_ENV = join(ROOT, '__jest_env__')

module.exports = {
  verbose: true,
  transform: {
    '^.+\\.jsx?$': 'babel-jest'
  },
  globalSetup: join(JEST_ENV, 'setup.js'),
  globalTeardown: join(JEST_ENV, 'teardown.js'),
  testEnvironment: join(JEST_ENV, 'server-environment.js')
}

Small typo here on your yarn commands.

yarn add babel-register babel-polyfill

Based on @AlvSovereign and @sourcec0de work, adding

const { readFileSync } = require('fs');
const babelConfig = require('./babel.config.js');
require('@babel/register')(babelConfig);

in your jest.config.js worked for me

No need for workarounds, the feature has been implemented

You said it was implemented in jest 24, but we are using jest 24.8.0 and if I don't do that, it does not work. Am i missing something?

Also, in the documentation, this is still present:

Note: While code transformation is applied to the linked setup-file, Jest will not transform any code in node_modules. This is due to the need to load the actual transformers (e.g. babel or typescript) to perform transformation.

Transforming node_modules is not the same as Make "globalSetup" and "globalTeardown" work together with "transform", which is what this issue is about.

EDIT: We also transform everything not ignore via transformIgnorePatterns since 24.6: #8143. It should have updated the docs, though

Thanks.
Indeed these 2 subjects got mixed in this issue, that's why I replied in it, sorry.

I was not able to make it work using transformIgnorePatterns: []. I'll try some other things and if I can't find a solution, maybe I'll open a new issue.

Variable added to global in global{Setup,Teardown} cannot be used in test cases?
I add a key to global in globalSetup file, but i cannot get it from my test cases.
And it seems that, there're two different globals, the first one is instanceof NodeJS.Global, howere the second one is Window.

// globalSetup.ts
module.exports = async () => {
  (global as any).aaa = 111;
}

// testcase.test.ts
console.log('>>global', typeof global, (global as any).aaa);

@vifird that is correct, each test runs in its own sandboxed environment.

@jeysal So is there a way to add global settings? except "globals" field in jest's configuration.

Correct. We might add a way to send variables from globalSetup to tests (there are some issues about it, but I'm currently on mobile) but that's not in place yet

@SimenB
i still can't use the import statement like import vue from 'vue' in the file which globalSetup point to

i have an error on my terminal without any extra information

Determining test suites to run...

SyntaxError: Unexpected identifier

Did i missing something ?
And what are u meaning about "_This is available in Jest 24_" ?
i thought i don't need to config anything and i can have such feature


below is my _jest.config.js_

module.exports = {
    globalSetup: './tests/unit/run-once-before-all-files.js',

    moduleFileExtensions: [
        'js',
        'jsx',
        'json',
        'vue'
    ],
    transform: {
        '^.+\\.vue$': 'vue-jest',
        '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub',
        '^.+\\.jsx?$': 'babel-jest'
    },

    transformIgnorePatterns: [
        '/node_modules/',
        '/public/',
        '/__mocks__/',
        '/api/',
        '/src/assets/',
        'tests/e2e/'
    ],
    moduleNameMapper: {
        '^@/(.*)$': '<rootDir>/src/$1'
    },
    snapshotSerializers: [
        'jest-serializer-vue'
    ],
    testMatch: [
        '**/tests/unit/**/*.test.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
    ],
    testURL: 'http://localhost/',
    watchPlugins: [
        'jest-watch-typeahead/filename',
        'jest-watch-typeahead/testname'
    ],
    'setupFiles': ['jest-canvas-mock']
}

using Jest 24.9 and had to require('ts-node/register) to get it working too. I don't understand how this is available in Jest 24

I can confirm this is not working in 24.9. As a matter of fact, it only works up to 24.1 for me.

Related: #9041

Please open up new issues (with reproductions) if you have a regression to report - this old issue is not the place for it

Was this page helpful?
0 / 5 - 0 ratings