How to stub process.env in node.js? - node.js

I want to stub process.env.FOO with bar.
var sinon = require('sinon');
var stub = sinon.stub(process.env, 'FOO', 'bar');
I'm confused.
I read document, but still I don't understand yet.sinonjs docs
sinonjs is one example, not sinonjs is okay.

From my understanding of process.env, you can simply treat it like any other variable when setting its properties. Keep in mind, though, that every value in process.env must be a string. So, if you need a particular value in your test:
it('does something interesting', () => {
process.env.NODE_ENV = 'test';
// ...
});
To avoid leaking state into other tests, be sure to reset the variable to its original value or delete it altogether:
afterEach(() => {
delete process.env.NODE_ENV;
});

I was able to get process.env to be stubed properly in my unit tests by cloning it and in a teardown method restoring it.
Example using Mocha
const env = Object.assign({}, process.env);
after(() => {
process.env = env;
});
...
it('my test', ()=> {
process.env.NODE_ENV = 'blah'
})
Keep in mind this will only work if the process.env is only being read in the function you are testing. For example if the code that you are testing reads the variable and uses it in a closure it will not work. You probably invalidate the cached require to test that properly.
For example the following won't have the env stubbed:
const nodeEnv = process.env.NODE_ENV;
const fnToTest = () => {
nodeEnv ...
}

With sinon you can stub any variable like this.
const myObj = {
example: 'oldValue',
};
sinon.stub(myObj, 'example').value('newValue');
myObj.example; // 'newValue'
This example is form sinon documentation. https://sinonjs.org/releases/v6.1.5/stubs/
With that knowledge, you can stub any environment variable.
In your case it would look like this:
let stub = sinon.stub(process.env, 'FOO').value('bar');

You can use this if you want to stub a key which not present in process.env
const sinon = require('sinon')
let sandbox = sinon.createSandbox();
sandbox.stub(process, 'env').value({ 'SOME_KEY': 'SOME_VALUE' });

How to quickly mock process.env during unit testing.
https://glebbahmutov.com/blog/mocking-process-env/
const sinon = require('sinon')
let sandbox = sinon.createSandbox()
beforeEach(() => {
sandbox.stub(process.env, 'USER').value('test-user')
})
it('has expected user', () => {
assert(process.env.USER === 'test-user', 'wrong user')
})
afterEach(() => {
sandbox.restore()
})
But what about properties that might not exist in process.env before the test? You can use the following package and then you will be able to test the not exist env variables.
https://github.com/bahmutov/mocked-env

In a spec-helper.coffee or something similar where you set up your sinon sandbox, keep track of the original process.env and restore it after each test, so you don't leak between tests and don't have to remember to reset every time.
_ = require 'lodash'
sinon = require 'sinon'
beforeEach ->
#originalProcessEnv = _.cloneDeep process.env
afterEach ->
process.env = _.cloneDeep #originalProcessEnv
In your test, use process.env as normal.
it 'does something based on an env var', ->
process.env.FOO = 'bar'

Like many others have pointed out the sinon way works unless you have the environment variables set before the test begins, e.g. in the file you are testing like so:
const foo = process.env.YOUR_VAR;
Gleb Bahmutov wrote an npm package that gives a nice way of setting environment variables in your tests no matter how you are referencing the environment variables.
Link to his page: https://glebbahmutov.com/blog/mocking-process-env/
Link to npm package: https://github.com/bahmutov/mocked-env
It worked great and was super easy to implement.

Related

Does Rewire set the current running module variables as well as the specified module variables?

So, I was doing some testing with Rewire with Mocha, and I noticed behavior that seems odd to me. using myModule.__set__() seems to actually set the specified variable in the the specified module (myModule), AND in the global scope of the current module (the one that ran the __set__()). For example:
This code runs after running mocha test:
Test.js:
var rewire = require("rewire")
var sinon = require("sinon")
var test2 = rewire("./test2.js")
var expect = require("chai").expect
var spy = sinon.spy()
describe("test", function () {
beforeEach(function () {
test2.__set__("console", { log: spy })
})
it("test should be equal to 3", function () {
test2.test1()
console.log("testing5")
expect(spy.callCount).to.equal(3)
})
})
Test2.js:
module.exports = {
test1() {
console.log("test")
console.log("test")
console.log("test")
}
}
Here, I would expect "testing5" to actually be logged to the console, but the 3 "test"s to just be recorded in the spy, and the callCount to be equal to 3. This is not what is happening however. The "testing5" is not being logged, but is being recorded to that sinon spy, and the test is failing, because the callCount is 4, and not 3. To me this doesn't seem like this would be intended. Is there something I'm doing wrong?

Is there a way to change the mocked value of a required dependency?

I'm facing a problem I'm not able to resolve on my own, maybe some of you faced the same problem.
Let me show you what I'm trying to do, here is the mock:
let mockConfig = {name: 'dude'};
jest.mock('../../../configManager', () => mockConfig);
configManager is a dependency of the function I'm trying to test.
It works well but I want to change the returning object of configManager in another test so the tested function behaves differently.
Let me show you, here is the function I'm testing:
const config = require('../../../configManager');
module.exports = () => {
if (config.name === 'dude') {
do stuff;
}
if (config.name === 'dudette') {
do something else;
}
So, typically, I want to change the config.name to 'dudette' to be able to test the second part of my function.
Naturally, when I want to do this with an imported function, I just do:
let mockJsonQueryResult = { value: 'stuff' };
jest.mock('json-query', () => jest.fn(() => mockJsonQueryResult));
and then in the test, I directly set another value to mockJsonQueryResult:
mockJsonQueryResult = { value: 'hotterStuff' };
But I don't find any way of doing this with a dependency that returns an object, with a dependency returning a function, no problem.
Is there even any way of doing this?
Thanks in advance!
Edit: this is not the same as how to change jest mock function return value in each test? as #Dor Shinar suggested because his problem is to mock a function, even if it is inside a returning object it is still a function, I just want to change a value inside the returned object.
So, I found a solution I'm not completely satisfied with but it works:
I simply set the original full object and then for my tests, change the value of specific properties by setting them directly before calling the function I want to test.
example:
let mockConfig = { person: { name: 'dude', origin: {country: 'France'} } };
jest.mock('../../../configManager', () => mockConfig);
mockConfig.person = {};
mockConfig.person.name = 'dudette';
You don't need to mock the module at all.
If your module export is just an object with property values then just change the properties as needed.
Here is a simple working example to demonstrate:
configManager.js
module.exports = {
name: 'original'
}
code.js
const config = require('./configManager');
module.exports = () => `name: ${config.name}`;
code.test.js
const config = require('./configManager');
const func = require('./code');
test('func', () => {
expect(func()).toBe('name: original'); // Success!
config.name = 'dude';
expect(func()).toBe('name: dude'); // Success!
config.name = 'dudette';
expect(func()).toBe('name: dudette'); // Success!
})
Details
A module binding can't be directly changed to something else:
const config = require('./configManager');
config = { name: 'mock' }; // <= this doesn't work
...but you can change the properties of an object representing a module binding:
const config = require('./configManager');
config.name = 'mock'; // <= this works!
...and any code using the module will automatically see the changes.

Test process.env with Jest

I have an application that depends on environmental variables like:
const APP_PORT = process.env.APP_PORT || 8080;
And I would like to test that for example:
APP_PORT can be set by a Node.js environment variable.
or that an Express.js application is running on the port set with process.env.APP_PORT
How can I achieve this with Jest? Can I set these process.env variables before each test or should I mock it somehow maybe?
The way I did it can be found in this Stack Overflow question.
It is important to use resetModules before each test and then dynamically import the module inside the test:
describe('environmental variables', () => {
const OLD_ENV = process.env;
beforeEach(() => {
jest.resetModules() // Most important - it clears the cache
process.env = { ...OLD_ENV }; // Make a copy
});
afterAll(() => {
process.env = OLD_ENV; // Restore old environment
});
test('will receive process.env variables', () => {
// Set the 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 testedModule = require('../../config/env').default
// ... actual testing
});
});
If you look for a way to load environment values before running the Jest look for the answer below. You should use setupFiles for that.
Jest's setupFiles is the proper way to handle this, and you need not install dotenv, nor use an .env file at all, to make it work.
jest.config.js:
module.exports = {
setupFiles: ["<rootDir>/.jest/setEnvVars.js"]
};
.jest/setEnvVars.js:
process.env.MY_CUSTOM_TEST_ENV_VAR = 'foo'
That's it.
Another option is to add it to the jest.config.js file after the module.exports definition:
process.env = Object.assign(process.env, {
VAR_NAME: 'varValue',
VAR_NAME_2: 'varValue2'
});
This way it's not necessary to define the environment variables in each .spec file and they can be adjusted globally.
In ./package.json:
"jest": {
"setupFiles": [
"<rootDir>/jest/setEnvVars.js"
]
}
In ./jest/setEnvVars.js:
process.env.SOME_VAR = 'value';
You can use the setupFiles feature of the Jest configuration. As the documentation said that,
A list of paths to modules that run some code to configure or set up
the testing environment. Each setupFile will be run once per test
file. Since every test runs in its own environment, these scripts will
be executed in the testing environment immediately before executing
the test code itself.
npm install dotenv dotenv that uses to access environment variable.
Create your .env file to the root directory of your application and add this line into it:
#.env
APP_PORT=8080
Create your custom module file as its name being someModuleForTest.js and add this line into it:
// someModuleForTest.js
require("dotenv").config()
Update your jest.config.js file like this:
module.exports = {
setupFiles: ["./someModuleForTest"]
}
You can access an environment variable within all test blocks.
test("Some test name", () => {
expect(process.env.APP_PORT).toBe("8080")
})
Expanding a bit on Serhan C.'s answer...
According to the blog post How to Setup dotenv with Jest Testing - In-depth Explanation, you can include "dotenv/config" directly in setupFiles, without having to create and reference an external script that calls require("dotenv").config().
I.e., simply do
module.exports = {
setupFiles: ["dotenv/config"]
}
In test file:
const APP_PORT = process.env.APP_PORT || 8080;
In the test script of ./package.json:
"scripts": {
"test": "jest --setupFiles dotenv/config",
}
In ./env:
APP_PORT=8080
In my opinion, it's much cleaner and easier to understand if you extract the retrieval of environment variables into a utility (you probably want to include a check to fail fast if an environment variable is not set anyway), and then you can just mock the utility.
// util.js
exports.getEnv = (key) => {
const value = process.env[key];
if (value === undefined) {
throw new Error(`Missing required environment variable ${key}`);
}
return value;
};
// app.test.js
const util = require('./util');
jest.mock('./util');
util.getEnv.mockImplementation(key => `fake-${key}`);
test('test', () => {...});
Depending on how you can organize your code, another option can be to put the environment variable within a function that's executed at runtime.
In this file, the environment variable is set at import time and requires dynamic requires in order to test different environment variables (as described in this answer):
const env = process.env.MY_ENV_VAR;
const envMessage = () => `MY_ENV_VAR is set to ${env}!`;
export default myModule;
In this file, the environment variable is set at envMessage execution time, and you should be able to mutate process.env directly in your tests:
const envMessage = () => {
const env = process.env.MY_VAR;
return `MY_ENV_VAR is set to ${env}!`;
}
export default myModule;
Jest test:
const vals = [
'ONE',
'TWO',
'THREE',
];
vals.forEach((val) => {
it(`Returns the correct string for each ${val} value`, () => {
process.env.MY_VAR = val;
expect(envMessage()).toEqual(...
you can import this in your jest.config.js
require('dotenv').config()
this work for me
All the above methods work if you're using require("dotenv").config within the jest.config.js file, a NodeJS application without TypeScript such as what Jialx or Henry Tipantuna has suggested.
But if you're using ts-jest and within the jest.config.ts file.
import dotenv from "dotenv"
dotenv.config()
/* config options below */
When using Typescript the following works for me:
in root:
jest.config.js
/* eslint-disable #typescript-eslint/no-var-requires */
const { pathsToModuleNameMapper } = require('ts-jest');
const { compilerOptions } = require('./tsconfig.paths.json');
module.exports = {
// [...]
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { prefix: '<rootDir>/' }),
};
process.env = Object.assign(process.env, {
env_name: 'dev',
another_var: 'abc123',
});
To build upon #HenryTipantuña's suggestion is to import dotenv in your jest.config.js and use a .env.test file in the config path
require('dotenv').config({
path: '.env.test'
})
Building on top of #jahller's answer.
I made it responsive so you don't need to keep the files in sync as things change.
Put this at the bottom of your jest.config.js file.
const arr = require('fs')
.readFileSync('.env', 'utf8')
.split('\n')
.reduce((vars, i) => {
const [variable, value] = i.split('=')
vars[variable] = value
return vars
}, {})
process.env = Object.assign(process.env, arr)
It reads the contents of your .env file, splits every new line and reduces it all back down to an object where you then assign it to process.env
OR
just use dotenv in jest.setup.js 🤷‍♂️
i have most simple for implementation env (specialy test.env)
require("dotenv").config({ path: './test.env' });
const { sum } = require('./sum.js');
describe('sum', () => {
beforeEach(() => {
jest.resetModules(); // remove cache
})
test('should success', () => {
expect(sum(1, 3)).toEqual(4);
})
})
I think you could try this too:
const currentEnv = process.env;
process.env = { ENV_NODE: 'whatever' };
// test code...
process.env = currentEnv;
This works for me and you don't need module things

Jest beforeAll() share between multiple test files

I have a Node.js project that I'm testing using Jest. I have several test files that have the same setup requirement. Previously, all these tests were in one file, so I just had a beforeAll(...) that performed the common setup. Now, with the tests split into multiple files, it seems like I have to copy/paste that beforeAll(...) code into each of the files. That seems inelegant - is there a better way to do this, ideally where I can just write my beforeAll(...)/setup logic once, and "require" it from multiple test files? Note that there are other tests in my test suite that don't require this setup functionality, so I don't want to make all my tests run this setup (just a particular subset of test files).
If you're using Jest >=20, you might want to look into creating a custom jest-environment for the tests that require this common setup. This would be a module that extends either jest-environment-node or jest-environment-jsdom, and implements async setup(), async teardown(), and async runScript() to do this setup work.
You can then add a #jest-environment my-custom-env directive to those files that require this setup.
See the Jest config docs for testEnvironment for details on how to set this up; there's a simple example there.
I am using a simple "test hooks" pattern for this:
// This function wraps beforeAll and afterAll into a single RAII-like call.
// That makes the describe code further down easier to read and makes
// sure you don't forget the afterAll part. Can easily be shared between tests.
function useFakeServer() {
let server;
beforeAll(() => server = sinon.fakeServer.create());
afterAll(() => server.restore());
return () => server;
}
describe('Some scenario', () => {
const getServer = useFakeServer();
it('accesses the server', () => {
const server = getServer();
// Test as you normally would..
expect(server.requests[0]. /* ... */);
});
});
If you need a script to run before all your test files, you can use globalSetup
This option allows the use of a custom global setup module which exports an async function that is triggered once before all test suites.
in your jest.config.js
//jest.config.js
module.exports = {
...
testTimeout: 20000,
globalSetup: "./setup.js"
};
then create a file named setup.js
// setup.js
module.exports = async () => {
console.log("I'll be called first before any test cases run");
//add in what you need to do here
};
Docs
You can move your beforeAll logic into one file and reference it in jest.config.js setupFilesAfterEnv section:
module.exports = {
...
setupFilesAfterEnv: ['<rootDir>/testHelper.ts'],
...
}
https://jestjs.io/docs/en/configuration#setupfilesafterenv-array
Create a function somewhere like so:
export function setupBeforeAndAfter(putParamsHereIfYouHaveAny) {
beforeAll(() => shared-before-all-code);
afterAll(() => shared-after-all-code);
beforeEach(() => shared-before-each-code);
afterEach(() => shared-after-each-code);
}
Then just call it wherever you would otherwise have manually written these functions:
describe('My test', () => {
setupBeforeAndAfter(putParamsHereIfYouHaveAny)
it('is amazing', () => {
// Stuff in setupBeforeAndAfter() will run before/after this test as appropriate
})
})

How do I override config values at runtime with node-config?

I'd like to override some values at test-time, specifically setting my retries for an http service to 1 (immediate failure, no retries). Our project uses node-config. According to the docs I can override with NODE_CONFIG env variable:
node myapp.js --NODE_CONFIG='{"Customer":{"dbConfig":{"host":"customerdb.prod"}}}'
Well I would prefer to do this in my test, but not for all tests. The code says that you can allow config mutations by setting ALLOW_CONFIG_MUTATIONS.
process.env.ALLOW_CONFIG_MUTATIONS = "true";
const importFresh = require('import-fresh');
importFresh("config");
process.env.NODE_CONFIG = JSON.stringify({httpServices:{integration:{enrich: {retryInterval: 1, retries: 1}}}});
expect(process.env.NODE_CONFIG, 'NODE_CONFIG not set').to.exist();
expect(process.env.NODE_CONFIG, 'NODE_CONFIG not set').to.match(/retryInterval/);
expect(process.env.ALLOW_CONFIG_MUTATIONS, 'ALLOW_CONFIG_MUTATIONS not set').to.equal("true");
const testConfig = require("config");
console.dir(testConfig.get("httpServices.integration.enrich"));
expect(testConfig.get("httpServices.integration.enrich.retryInterval"), 'config value not set to 1').to.equal(1);
Result:
{ url: 'https://internal-**********',
retryInterval: 5000,
retries: 5 }
`Error: config value not set to 1: Expected 5000 to equal specified value: 1`
How do I get this override to work?
(expect is from Hapi.js Code library)
I'm one of the maintainers of node-config. Your bug is that you used require the second time when you should have used importFresh again.
Your first use of "importFresh()" does nothing different than require() would, because it is the first use of require().
After setting some variables, you call require(), which will return the copy of config already generated and cached, ignoring the effects of the environment variables set.
You only needed to use importFresh() once, where you currently use require(). This will cause a "fresh" copy of the config object to be returned, as you expected.
Simply changing config's property worked for me.
For example:
const config = require( 'config' );
config.httpServices.integration.enrich.retryInterval = 1;
// Do your tests...
UPD: Make sure that overrides are done before anyone calls the first config.get(), because the config object is made immutable as soon as any client uses the values via get().
Joining late, but other answers did not fit with the testing standard in my project, so here is what I came up with
TL;DR
Use mocks..
Detailed Answer
node-config uses a function get to get the configuration values.
By mocking the function get you can easily modify any configuration you see fit..
My personal favorite library is sinon
Here is an implementation of a mock with sinon
const config = require('config');
const sinon = require('sinon');
class MockConfig {
constructor () {
this.params = {};
this.sandbox = sinon.sandbox.create();
}
withConfValue (confKey, confValue) {
this.params.confValues[confKey] = confValue;
return this;
}
reset () {
this.params.confValues: {};
return this;
}
restore() {
this.sandbox.restore();
}
apply () {
this.restore(); // avoid duplicate wrapping
this.sandbox.stub(config, 'get').callsFake((configKey) => {
if (this.params.confValues.hasOwnProperty(configKey)) {
return this.params.confValues[configKey];
}
// not ideal.. however `wrappedMethod` approach did not work for me
// https://stackoverflow.com/a/57017971/1068746
return configKey
.split('.')
.reduce((result, item) => result[item], config)
});
}
}
const instance = new MockConfig();
MockConfig.instance = () => instance;
module.exports = MockConfig;
Usage would be
const mockConfig = require('./mock_config').instance();
...
beforeEach(function () {
mockConfig.reset().apply();
})
afterEach(function () {
mockConfig.reset().clear();
})
it('should do something') {
mockConfig.withConfValue('some_topic.some_field.property', someValue);
... rest of the test ...
}
Assumptions
The only assumption this approach makes is that you adhere to node-config way of reading the configuration (using the get function) and not bypass it by accessing fields directly.
It's better to create a development.json, production.json et test.json in your config folder node-config will use it your app configuration.
you just net to set your NODE_ENV to use the specific file.
Hope it helps :)

Resources