如何对需要其他模块的 Node.js 模块进行单元测试以及如何模拟全局 require 函数?
这是一个简单的示例,说明了我的问题的症结:
var innerLib = require('./path/to/innerLib');
function underTest() {
return innerLib.doComplexStuff();
}
module.exports = underTest;
我正在尝试为此代码编写单元测试。如何在不完全模拟 require
函数的情况下模拟对 innerLib
的需求?
所以这是我试图模拟全局 require
并发现即使这样做也行不通:
var path = require('path'),
vm = require('vm'),
fs = require('fs'),
indexPath = path.join(__dirname, './underTest');
var globalRequire = require;
require = function(name) {
console.log('require: ' + name);
switch(name) {
case 'connect':
case indexPath:
return globalRequire(name);
break;
}
};
问题是 require
函数位于 >underTest.js
文件实际上还没有被模拟出来。它仍然指向全局 require
函数。所以看来我只能在我进行模拟的同一个文件中模拟 require
函数。如果我使用全局 require
来包含任何内容,即使在之后我已经覆盖了本地副本,所需的文件仍将具有全局 require
引用。
This is a trivial example that illustrates the crux of my problem:
var innerLib = require('./path/to/innerLib');
function underTest() {
return innerLib.doComplexStuff();
}
module.exports = underTest;
I am trying to write a unit test for this code. How can I mock out the requirement for the innerLib
without mocking out the require
function entirely?
So this is me trying to mock out the global require
and finding out that it won’t work even to do that:
var path = require('path'),
vm = require('vm'),
fs = require('fs'),
indexPath = path.join(__dirname, './underTest');
var globalRequire = require;
require = function(name) {
console.log('require: ' + name);
switch(name) {
case 'connect':
case indexPath:
return globalRequire(name);
break;
}
};
The problem is that the require
function inside the underTest.js
file has actually not been mocked out. It still points to the global require
function. So it seems that I can only mock out the require
function within the same file I’m doing the mocking in. If I use the global require
to include anything, even after I’ve overridden the local copy, the files being required will still have the global require
reference.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(10)
使用 NodeJS 很简单。这是我在这个名为
mock
的库中使用的整个模拟函数:像这样使用它:
It's simple using NodeJS. Here's the entire mocking function I'm using in this library called
mock
:Use it like this:
该函数调用一个具有其所有依赖项的函数:
想要测试以下函数:
我将按如下方式设置我的测试(我使用 Jest):
我使用一个简单的工厂,它返回一个函数, sstatic.net/ZZNmY.png" rel="nofollow noreferrer">查看测试结果
在生产代码中,我将手动注入被调用者的依赖项,如下所示:
我倾向于限制我编写的大多数模块的范围到一件事,这减少了测试并将其集成到项目中时必须考虑的依赖项数量。
这就是我处理依赖关系的方法。
I use a simple factory the returns a function that calls a function with all of its dependencies:
Wanting to test the following function:
I would setup my test (I use Jest) as follows:
See results of test
In production code I would manually inject the callee's dependencies as below:
I tend to limit the scope of most of the modules that I write to one thing, which reduces the number of dependencies that have to be accounted for when testing and integrating them into the project.
And that's my approach to dealing with dependencies.
现在你可以了!
我发布了 proxyquire ,它将在您测试模块时覆盖模块内的全局需求。
这意味着您无需更改代码即可为所需模块注入模拟。
Proxyquire 有一个非常简单的 api,它允许解析您尝试测试的模块,并通过一个简单的步骤传递其所需模块的模拟/存根。
@Raynos 是对的,传统上你必须诉诸不太理想的解决方案才能实现这一目标,或者进行自下而上的开发,
这就是我创建 proxyquire 的主要原因 - 允许自上而下的测试驱动开发,没有任何麻烦。
查看文档和示例,以确定它是否适合您的需求。
You can now!
I published proxyquire which will take care of overriding the global require inside your module while you are testing it.
This means you need no changes to your code in order to inject mocks for required modules.
Proxyquire has a very simple api which allows resolving the module you are trying to test and pass along mocks/stubs for its required modules in one simple step.
@Raynos is right that traditionally you had to resort to not very ideal solutions in order to achieve that or do bottom-up development instead
Which is the main reason why I created proxyquire - to allow top-down test driven development without any hassle.
Have a look at the documentation and the examples in order to gauge if it will fit your needs.
在这种情况下,更好的选择是模拟返回的模块的方法。
无论好坏,大多数 Node.js 模块都是单例的; require() 同一模块的两段代码获得对该模块的相同引用。
您可以利用这一点并使用诸如 sinon 之类的东西来模拟所需的项目。 mocha 测试如下:
Sinon 具有良好的与 chai 集成用于进行断言,我编写了一个模块 将 sinon 与 mocha 集成,以便更轻松地进行间谍/存根清理(以避免测试污染。)
请注意,underTest 不能以相同的方式进行模拟,因为 underTest 仅返回一个函数。
另一种选择是使用 Jest 模拟。关注他们的页面
A better option in this case is to mock methods of the module that gets returned.
For better or worse, most node.js modules are singletons; two pieces of code that require() the same module get the same reference to that module.
You can leverage this and use something like sinon to mock out items that are required. mocha test follows:
Sinon has good integration with chai for making assertions, and I wrote a module to integrate sinon with mocha to allow for easier spy/stub cleanup (to avoid test pollution.)
Note that underTest cannot be mocked in the same way, as underTest returns only a function.
Another option is to use Jest mocks. Follow up on their page
我使用 mock-require。确保在
要求
要测试的模块之前定义了模拟。I use mock-require. Make sure you define your mocks before you
require
the module to be tested.为好奇的人模拟模块的简单代码
请注意操作 require.cache 的部分,并注意 require.resolve 方法,因为这是秘密武器。
使用类似:
但是... jest 内置了此功能,我建议您使用测试框架来进行测试。
Simple code to mock modules for the curious
Notice the parts where you manipulate the
require.cache
and noterequire.resolve
method as this is the secret sauce.Use like:
BUT... jest has this functionality built in and I recommend that testing framework over rolling your own for testing purposes.
对我来说,嘲笑
require
感觉就像是一种令人讨厌的黑客行为。我个人会尝试避免它并重构代码以使其更易于测试。有多种方法可以处理依赖关系。
1)将依赖项作为参数传递
这将使代码普遍可测试。缺点是您需要传递依赖项,这会使代码看起来更复杂。
2)将模块实现为类,然后使用类方法/属性来获取依赖项
(这是一个人为的示例,其中类的使用并不合理,但它传达了想法)
(ES6 示例)
现在您可以轻松地存根
getInnerLib
方法来测试您的代码。代码变得更加冗长,但也更容易测试。
Mocking
require
feels like a nasty hack to me. I would personally try to avoid it and refactor the code to make it more testable.There are various approaches to handle dependencies.
1) pass dependencies as arguments
This will make the code universally testable. The downside is that you need to pass dependencies around, which can make the code look more complicated.
2) implement the module as a class, then use class methods/ properties to obtain dependencies
(This is a contrived example, where class usage is not reasonable, but it conveys the idea)
(ES6 example)
Now you can easily stub
getInnerLib
method to test your code.The code becomes more verbose, but also easier to test.
如果您曾经使用过 jest,那么您可能会熟悉 jest 的模拟功能。
使用“jest.mock(...)”,您可以简单地指定代码中某处的 require 语句中出现的字符串,并且每当需要使用该字符串的模块时,都会返回一个模拟对象。
例如,
将用从该“工厂”函数返回的对象完全替换“firebase-admin”的所有导入/需求。
好吧,你可以在使用 jest 时这样做,因为 jest 围绕它运行的每个模块创建一个运行时,并将“挂钩”版本的 require 注入到模块中,但如果没有 jest,你将无法做到这一点。
我尝试使用 mock-require 来实现此目的,但对我来说它不起作用对于我的源代码中的嵌套级别。查看 github 上的以下问题: mock-require 并不总是用 Mocha 调用。
为了解决这个问题,我创建了两个 npm 模块,您可以使用它们来实现您想要的目标。
你需要一个 babel-plugin 和一个 module mocker。
在你的 .babelrc 中使用带有以下选项的 babel-plugin-mock-require 插件:
并在你的测试中文件使用 jestlike-mock 模块,如下所示:
jestlike-mock
模块仍然非常初级,没有很多文档,但也没有太多代码。我很感谢任何 PR 提供更完整的功能集。目标是重新创建整个“jest.mock”功能。为了了解 jest 是如何实现的,可以查找“jest-runtime”包中的代码。请参阅 https://github.com例如 /facebook/jest/blob/master/packages/jest-runtime/src/index.js#L734,这里它们生成模块的“automock”。
希望有帮助;)
If you've ever used jest, then you're probably familiar with jest's mock feature.
Using "jest.mock(...)" you can simply specify the string that would occur in a require-statement in your code somewhere and whenever a module is required using that string a mock-object would be returned instead.
For example
would completely replace all imports/requires of "firebase-admin" with the object you returned from that "factory"-function.
Well, you can do that when using jest because jest creates a runtime around every module it runs and injects a "hooked" version of require into the module, but you wouldn't be able to do this without jest.
I have tried to achieve this with mock-require but for me it didn't work for nested levels in my source. Have a look at the following issue on github: mock-require not always called with Mocha.
To address this I have created two npm-modules you can use to achieve what you want.
You need one babel-plugin and a module mocker.
In your .babelrc use the babel-plugin-mock-require plugin with following options:
and in your test file use the jestlike-mock module like so:
The
jestlike-mock
module is still very rudimental and does not have a lot of documentation but there's not much code either. I appreciate any PRs for a more complete feature set. The goal would be to recreate the whole "jest.mock" feature.In order to see how jest implements that one can look up the code in the "jest-runtime" package. See https://github.com/facebook/jest/blob/master/packages/jest-runtime/src/index.js#L734 for example, here they generate an "automock" of a module.
Hope that helps ;)
你不能。您必须构建单元测试套件,以便首先测试最低的模块,然后再测试需要模块的较高级别的模块。
您还必须假设任何第 3 方代码和 Node.js 本身都经过了良好的测试。
我想您会在不久的将来看到模拟框架,它们会覆盖
global.require
如果您确实必须注入模拟,您可以更改代码以公开模块化范围。
请注意,这会将
.__module
暴露到您的 API 中,任何代码都可以访问模块化范围,但会带来危险。You can't. You have to build up your unit test suite so that the lowest modules are tested first and that the higher level modules that require modules are tested afterwards.
You also have to assume that any 3rd party code and node.js itself is well tested.
I presume you'll see mocking frameworks arrive in the near future that overwrite
global.require
If you really must inject a mock you can change your code to expose modular scope.
Be warned this exposes
.__module
into your API and any code can access modular scope at their own danger.您可以使用 mockery 库:
You can use mockery library: