103

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.

Bonifacio2
  • 3,405
  • 6
  • 34
  • 54
Matt - sanemat
  • 5,418
  • 8
  • 37
  • 41
  • Can you explain why you would want to stub environment vars? Are you doing this on a unix-like OS or Windows? – slebetman Jul 05 '14 at 20:53
  • 2
    @slebetman it's common to rely on environment variables for configuration, like an API key for a service you rely on. See http://12factor.net/. – Andrew Homeyer Jul 30 '15 at 18:16
  • 1
    @AndrewHomeyer: Yes, but you don't **stub** them -- you set them correctly for the test – slebetman Jul 31 '15 at 00:34

7 Answers7

94

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;
   });
Joshua Dutton
  • 1,190
  • 8
  • 11
  • 9
    It works for me. One thing to keep in mind is that if you are testing a module that reads NODE_ENV when the module first loads, you will probably want to set NODE_ENV _before_ loading the module (i.e. NODE_ENV can be set in a beforeEach block.) This may seem obvious, but it has tripped me up before. – Terrence Feb 27 '17 at 16:03
  • If you're having problems, can you post a code snippet for someone to look at? I wrote my answer with the Mocha test runner's syntax in mind, but it should work with any other runner too (e.g. lab). – Joshua Dutton Mar 21 '17 at 15:26
  • 2
    This works, but I found a quirk when using `jest`. In my production code I assigned from env to a const (e.g. `const X = process.env.X`). The const was declared at the (ES) module scope, not the function scope. My tests always passed with `jest --watch` on retried test runs, but always failed on the first run. There is an ordering issue I don't fully understand here. Just make sure you are always reading directly from `process.env` in your production code (i.e. in a function), and aren't caching it at the module level. – Jesse Buchanan Oct 31 '17 at 23:42
  • 1
    this works well if you are evaluating the process.env in a function, but not if it is a constant. for example, I had `const myValue = process.env.value ? process.env.value : 'default'` would not work if you set process.env.value inside a test. However, `const myValue = () => (process.env.value ? process.env.value : 'default'`) works as expected! – Rafael Marques Sep 26 '19 at 09:02
  • In this same vein, I had: `const SWITCH_ON = (process.env.SWITCH_ON.toLowerCase() === 'true');` which didn't work so I changed it to two lines: `var switchOn = process.env.SWITCH_ON; const SWITCH_ON = (switchOn === undefined ? false : switchOn.toLowerCase() === 'true');` The initial kept giving me `undefined` errors where I was doing the `.toLowerCase()` – Scala Enthusiast Nov 04 '19 at 23:23
  • the initial unit test gives process.env.USER=undefined and succeeding unit test has values. But i cannot set another value with the process.env.USER when I put inside it("another test case scenario") – Scott Jones Aug 14 '20 at 11:02
  • i dont know why but it doesnt work for me – Kunal Burangi Oct 21 '22 at 13:26
29

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 ...
}
pllee
  • 3,909
  • 2
  • 30
  • 33
  • 4
    This process mostly worked. I had to tweak the "after" method. `after(() => { process.env = Object.assign({}, env); });` Otherwise the tests would manipulate the shared copy. Need to set after each test a fresh version. – Kyle Nov 08 '17 at 19:16
  • 1
    @Kyle.. no it wouldn't? assuming you setup env once at the top of your file, it'll be restored to what it was at the beginning of your test suite.. – Prisoner Mar 16 '18 at 16:19
9

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');
MegaRoks
  • 898
  • 2
  • 13
  • 30
Getriax
  • 521
  • 1
  • 8
  • 11
  • 7
    I received an error "Cannot stub non-existent own property FOO". Also using wallaby.js though to run my tests. – Will Lovett Aug 29 '19 at 17:20
  • 1
    Thanks for posting the answer to the question "What does stubbing an env var look like?" instead of just saying we don't need to because we can manipulate them manually :) – Will Oct 16 '19 at 00:07
  • I had the same error as @WillLovett and solved it by adding the require call near the top of my unit-test script: `require('dotenv').config();` I realized that this normally gets called when my application runs, but if I am running my unit-tests directly, this require statement would be missing. – Von Pittman Nov 05 '20 at 08:27
9

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' });
6

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

4

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'
Andrew Homeyer
  • 7,937
  • 5
  • 33
  • 26
  • `underscore`'s `clone` function works in place of `cloneDeep` - useful if you're already using `underscore` rather than `lodash`. – Rob Feb 06 '19 at 16:55
0

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.

hanchan07
  • 1
  • 1