31

I have a file with object that gets populated with process.env properties:

env.js

console.log('LOADING env.js');

const {
  PROXY_PREFIX = '/api/',
  USE_PROXY = 'true',
  APP_PORT = '8080',
  API_URL = 'https://api.address.com/',
  NODE_ENV = 'production',
} = process.env;

const ENV = {
  PROXY_PREFIX,
  USE_PROXY,
  APP_PORT,
  API_URL,
  NODE_ENV,
};

module.exports.ENV = ENV;

Now I try to test this file with different process.env properties:

env.test.js

const envFilePath = '../../config/env';

describe('environmental variables', () => {
  const OLD_ENV = process.env;

  beforeEach(() => {
    process.env = { ...OLD_ENV };
    delete process.env.NODE_ENV;
  });

  afterEach(() => {
    process.env = OLD_ENV;
  });

  test('have default values', () => {
    const { ENV } = require(envFilePath);
    expect(ENV).toMatchSnapshot();
  });

  test('are string values (to avoid casting errors)', () => {
    const { ENV } = require(envFilePath);
    Object.values(ENV).forEach(val => expect(typeof val).toEqual('string'));
  });

  test('will receive process.env variables', () => {
    process.env.NODE_ENV = 'dev';
    process.env.PROXY_PREFIX = '/new-prefix/';
    process.env.API_URL = 'https://new-api.com/';
    process.env.APP_PORT = '7080';
    process.env.USE_PROXY = 'false';

    const { ENV } = require(envFilePath);

    expect(ENV.NODE_ENV).toEqual('dev');
    expect(ENV.PROXY_PREFIX).toEqual('/new-prefix/');
    expect(ENV.API_URL).toEqual('https://new-api.com/');
    expect(ENV.APP_PORT).toEqual('7080');
    expect(ENV.USE_PROXY).toEqual('false');
  });
});

Unfortunately, even though I try to load the file in every test separately the file gets loaded only once, making the third test fail with:

Expected value to equal:
  "dev"
Received:
  "production"

P.S. It doesn't fail when I run the test alone.

I also know that env.js loads only once because console.log('LOADING env.js'); gets fired only once.

I tried to invalidate Nodes cache like:

  beforeEach(() => {
    delete require.cache[require.resolve(envFilePath)];
    process.env = { ...OLD_ENV };
    delete process.env.NODE_ENV;
  });

but require.cache is empty {} before each test so it seems that Jest is somehow responsible for importing the file.

I also tried to run yarn jest --no-cache but didn't help.

So what I want is to load env.js before each test so I can test how it behaves with different node environmental variables.

jest@^22.0.4

Yangshun Tay
  • 49,270
  • 33
  • 114
  • 141
Tomasz Mularczyk
  • 34,501
  • 19
  • 112
  • 166

3 Answers3

50

You can use jest.resetModules() in beforeEach method to reset the already required modules

beforeEach(() => {
  jest.resetModules()
  process.env = { ...OLD_ENV };
  delete process.env.NODE_ENV;
});
Tomasz Mularczyk
  • 34,501
  • 19
  • 112
  • 166
Prakash Sharma
  • 15,542
  • 6
  • 30
  • 37
2

Instead of resetting all modules, you can require modules in isolation by using jest.isolateModules(fn).

For example:

test('are string values (to avoid casting errors)', () => {
  jest.isolateModules(() => {
    const { ENV } = require(envFilePath);
    Object.values(ENV).forEach(val => expect(typeof val).toEqual('string'));
  });
});
Koen.
  • 25,449
  • 7
  • 83
  • 78
1

jest version is: "jest": "^26.6.3". Other answers are correct but didn't explain the reason. The reason why delete require.cache[require.resolve(MODULE_PATH)] does NOT work is because jest doesn't implement the require.cache. See issue#5741.

We should use jest.resetModules() or jest.isolateModules(fn) instead.

E.g.

index.js:

module.exports = { name: 'main' };

index.test.js:

describe('71254501', () => {
  let obj1, obj2;
  beforeEach(() => {
    // This will work
    // jest.resetModules();
    // This doesn't work
    delete require.cache[require.resolve('.')];
  });
  test('should pass 1', () => {
    obj1 = require('.');
    obj1.name = 'b';
    console.log(obj1);
  });
  test('should pass 2', () => {
    obj2 = require('.');
    console.log(obj2);
  });
});

Test result:

 PASS  stackoverflow/71254501/index.test.js
  71254501
    ✓ should pass 1 (13 ms)
    ✓ should pass 2 (1 ms)

  console.log
    { name: 'b' }

      at Object.<anonymous> (stackoverflow/71254501/index.test.js:10:13)

  console.log
    { name: 'b' }

      at Object.<anonymous> (stackoverflow/71254501/index.test.js:14:13)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        0.606 s, estimated 8 s

As you can see, the require cache of module .is NOT deleted. When test case 1 mutates the obj, test case 2 still requires the same instance of the module which is not expected. We want a fresh module .

Lin Du
  • 88,126
  • 95
  • 281
  • 483