如何在node.js中对process.env进行存根?

103

我想使用bar替换process.env.FOO

var sinon = require('sinon');
var stub = sinon.stub(process.env, 'FOO', 'bar');

我很困惑。我读了这份文件,但我还是不明白。sinonjs 文档

sinonjs 只是一个例子,不用非得使用 sinonjs。


你能解释一下为什么要存根环境变量吗?你是在类UNIX操作系统还是Windows上做这个? - slebetman
2
@slebetman 通常会依赖环境变量来进行配置,比如你所依赖的某个服务的 API 密钥。请参考 http://12factor.net/。 - Andrew Homeyer
1
@AndrewHomeyer:是的,但你不会**存根(stub)**它们——你为测试设置它们的正确值。 - slebetman
7个回答

94

根据我对process.env的理解,当设置其属性时,您可以将其视为任何其他变量。但请记住,process.env中的每个值都必须是字符串。因此,如果您需要测试中的特定值:

   it('does something interesting', () => {
      process.env.NODE_ENV = 'test';
      // ...
   });

为了避免状态泄露到其他测试中,请确保将变量重置为其原始值或完全删除它:
   afterEach(() => {
       delete process.env.NODE_ENV;
   });

9
对我来说有效。需要记住的一件事是,如果您正在测试一个在模块首次加载时读取 NODE_ENV 的模块,那么您可能希望在加载模块之前设置 NODE_ENV(即 NODE_ENV 可以在 beforeEach 块中设置)。这似乎很明显,但我之前曾经被绊倒过。 - Terrence
2
这个代码是可行的,但我在使用jest时发现了一个小问题。在我的生产代码中,我将env赋值给了一个常量(例如const X = process.env.X)。该常量是在(ES)模块范围而不是函数范围内声明的。我的测试总是在jest --watch上通过重试测试运行,但在第一次运行时总是失败。这里有一个我不完全理解的排序问题。请确保您始终直接从process.env中读取您的生产代码(即在函数中),并且不要在模块级别缓存它。 - Jesse Buchanan
1
如果您在函数中评估process.env,则此方法效果很好,但如果它是一个常量,则不行。例如,我有const myValue = process.env.value ? process.env.value : 'default',如果您在测试中设置process.env.value,则无法正常工作。 然而,const myValue = () => (process.env.value ? process.env.value : 'default')可以按预期工作! - Rafael Marques
在同样的思路下,我有以下代码: const SWITCH_ON = (process.env.SWITCH_ON.toLowerCase() === 'true'); 但它没有起作用,所以我将其改为两行: var switchOn = process.env.SWITCH_ON; const SWITCH_ON = (switchOn === undefined ? false : switchOn.toLowerCase() === 'true'); 最初的代码一直给我返回 undefined 的错误,而我正在执行 .toLowerCase() 操作。 - Scala Enthusiast
我不知道为什么,但它对我不起作用。 - Kunal Burangi
显示剩余2条评论

29

我通过克隆 process.env 并在拆卸方法中恢复它,成功地将其正确地存根化,用于我的单元测试。

使用 Mocha 的示例

const env = Object.assign({}, process.env);

after(() => {
    process.env = env;
});

...

it('my test', ()=> {
    process.env.NODE_ENV = 'blah'
})

请记住,只有在您正在测试的函数中读取process.env时,此方法才有效。例如,如果您要测试的代码读取变量并在闭包中使用它,则此方法无效。您可能需要使require缓存失效以正确测试它。

例如下面的代码将不会使用env stubbed:

const nodeEnv = process.env.NODE_ENV;

const fnToTest = () => {
   nodeEnv ...
}

4
这个过程大部分有效。我需要调整“after”方法。 after(() => { process.env = Object.assign({}, env); });否则测试会操纵共享副本。每次测试都需要设置一个新版本。 - Kyle
1
@Kyle.. 不会的。假设你在文件顶部设置了环境一次,它将恢复到测试套件开始时的状态。 - Prisoner

9
使用sinon,您可以像这样存根任何变量。
 const myObj = {
    example: 'oldValue', 
 };

 sinon.stub(myObj, 'example').value('newValue');

 myObj.example; // 'newValue'

这个例子来自Sinon文档。 https://sinonjs.org/releases/v6.1.5/stubs/


有了这个知识,你可以为任何环境变量设置存根。 在你的情况下,它应该是这样的:

 let stub = sinon.stub(process.env, 'FOO').value('bar');

7
我收到了一个错误信息:“无法存根化不存在的自有属性 FOO”。同时,我也在使用wallaby.js来运行我的测试。 - Will Lovett
1
谢谢您发布关于“存根化环境变量是什么样子”的答案,而不是只说我们不需要这样做,因为我们可以手动操作它们 :) - Will
我遇到了与@WillLovett相同的错误,并通过在我的单元测试脚本顶部添加require调用来解决它:require('dotenv').config(); 我意识到这通常会在我的应用程序运行时调用,但如果我直接运行我的单元测试,则会缺少此require语句。 - Von Pittman

9

如果您想截取一个在process.env中不存在的键,可以使用这个方法。

const sinon = require('sinon')
let sandbox = sinon.createSandbox();
sandbox.stub(process, 'env').value({ 'SOME_KEY': 'SOME_VALUE' });

6
如何在单元测试中快速模拟process.env。 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()
})

那么对于在测试之前可能不存在于 process.env 中的属性怎么办呢?你可以使用以下软件包,然后就能够测试不存在的 env 变量了。

https://github.com/bahmutov/mocked-env


1
process.env.USER 没有值时,这个代码就行不通了。 - Sohail Si

4

在一个类似于spec-helper.coffee的文件中,你需要设置sinon沙盒,在此过程中要跟踪原始的process.env并在每次测试后还原它,这样可以避免测试之间的泄漏,并且不必每次都记得重置。

_ = require 'lodash'
sinon = require 'sinon'

beforeEach ->
    @originalProcessEnv = _.cloneDeep process.env

afterEach ->
    process.env = _.cloneDeep @originalProcessEnv

在你的测试中,正常使用process.env
it 'does something based on an env var', ->
    process.env.FOO = 'bar'

underscoreclone 函数可以替代 cloneDeep,如果你已经在使用 underscore 而不是 lodash,那么这个函数非常有用。 - Rob

0

正如许多人指出的那样,如果在测试开始之前设置了环境变量,例如在您正在测试的文件中这样做,则 sinon 方法将无效。

const foo = process.env.YOUR_VAR;

Gleb Bahmutov编写了一个npm包,以一种不错的方式设置环境变量,无论您如何引用环境变量。

他的页面链接:https://glebbahmutov.com/blog/mocking-process-env/

npm包链接:https://github.com/bahmutov/mocked-env

它运行良好且非常容易实现。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接