22

Before running e2e tests in Jest I need to get an authentication token from the server.

Is it possible to do this globally one and set it somehow to the global environment / context for each test ?

I tried it with globalSetup config option:

const auth = require('./src/auth')
const ctx = require('./src/context')

module.exports = () => {
    return new Promise(res => {
        auth.getToken()
            .then(token => {
                ctx.init({token})
                global.token = token
                res()
            })

    })
}

context.js

let _token

const init = ({token}) => {
    _token = token
}

const getToken = () => {
    return _token
}

module.exports = {
    init,
    getToken

}

But both global.token nor ctx.getToken() return undefined.

I need to use helper script and to pass token as env var which is then set as global.

  "scripts": {
    "test": "TOKEN=$(node src/get-token.js) jest"
  },

Is there a better approach (which does not involve shell) ?

ps-aux
  • 11,627
  • 25
  • 81
  • 128

3 Answers3

19

There's a CLI/config option called: setupFiles, it runs immediately before executing the test code itself.

"jest": {
  "globals": {
    "__DEV__": true,
    ...
  },
  "setupFiles": [
    "./setup.js"
  ]
}

setup.js might look something like:

(async function() {
    // const { authToken } = await fetchAuthTokens()
    global.authToken = '123'
})()

And then you can access authToken directly in each test suites (files) like beforeAll(_ => console.log(authToken)) // 123.


However, if you want a global setup that runs per worker(cpu core) instead of per test file (recommended by default since test suites are sandboxed). Based on current jest API constraints, you may choose to customize your test's runtime environment through the testEnvironment option. Then you need to implement a custom class that extends jsdom/node environment exposes setup/runScript/teardown api.

Example:

customEnvironment.js

// my-custom-environment
const NodeEnvironment = require('jest-environment-node');

class CustomEnvironment extends NodeEnvironment {
  constructor(config) {
    super(config);
  }

  async setup() {
    await super.setup();
    const token = await someSetupTasks();
    this.global.authToken = token;
  }

  async teardown() {
    await super.teardown();
    await someTeardownTasks();
  }

  runScript(script) {
    return super.runScript(script);
  }
}

my-test.js

let authToken;

beforeAll(() => {
  authToken = global.authToken;
});
Allen
  • 4,431
  • 2
  • 27
  • 39
  • 1
    Yep, but the globalSetup/globalTeardown API was **not** used to inject context as you want, it's mainly used for heavy instantiation tasks like launching browser instance and close it. – Allen Jan 30 '18 at 16:09
  • Can we use this with create-react-app? If yes, can you point a link please? – GarouDan Mar 14 '18 at 23:03
  • 14
    That worked only using the hardcoded value. When it comes to async function token is always undefined. – George Mylonas May 25 '18 at 12:08
  • 3
    **`if you want a global setup that runs per worker(cpu core) instead of per test file`**: The documentation says `TestEnvironment is sandboxed. Each test suite will trigger setup/teardown in their own TestEnvironment`: [https://jestjs.io/docs/en/configuration.html#testenvironment-string](https://jestjs.io/docs/en/configuration.html#testenvironment-string) – Evandro Coan Apr 16 '20 at 04:52
  • It is also the case the JS context is different in the `TestEnvironment` vs the tests. If you import and compare the imported instance between the `TestEnvironment` and the tests you will find the classes are not equivalently resolved (i.e. instanceof does not return true when it should). For the question submitted, the `TestEnvironment` should be sufficient however – James_Hill Dec 15 '21 at 15:25
4

Ini my case, I had to get the token for current session once. Here I used globalSetup property in jest config. Ref: https://jestjs.io/docs/en/configuration#globalsetup-string

My jest.config:

module.exports = {
  globalSetup: "./tests/global-setup.js",
  // ..other config... 
  globals: {
      TEST_EMAIL: 'test@test.com',
      TEST_PASSWORD: 'some_password',
  }
}

global-setup.js:

 module.exports = async function() {
  const getSessionToken = () => {
    // api call to get SessionToken
    /* return fetch() */
  }

  sessionToken = await getSessionToken();
  process.env.SESSION_TOKEN = sessionToken;
 }

Tabrez Basha
  • 172
  • 1
  • 5
0

I could find no way of skipping tests (test.skip) based on the result of an async condition. The tests determined if they were to be run before my global async condition resolved. I even tried using beforeAll and awaiting the resolution of my promise, but while in tests my condition had resolved, when the tests determined if they were to be run, the promise was unresolved and my var was undefined.

I ended up doing this with bash in scripts/isLocalCmsRunningOn3030.sh:

#!/bin/bash

if [[ $(lsof -i :3030 | grep 'node' | grep 'LISTEN') ]]; then
  echo TRUE
else
  echo FALSE
fi

package.json:

IS_CMS_RUNNING=$(scripts/isLocalCmsRunningOn3030.sh) jest

In my setupFile.js:

global.testIf = (condition) => (condition ? test : test.skip);

In my test file

  testIf(process.env.IS_CMS_RUNNING === 'TRUE')('testname', () => {
  });
Jeremy John
  • 13,686
  • 2
  • 16
  • 16