在 JavaScript ES6 模块中,可能有许多小的、易于测试的函数应该被测试,但不应该被导出。如何在不导出模块的情况下测试模块中的函数? (不使用重新接线)。
function shouldntBeExportedFn(){
// Does stuff that needs to be tested
// but is not for use outside of this package
}
export function exportedFn(){
// A function that should be called
// from code outside of this package and
// uses other functions in this package
}
export const exportedForTesting = {
shouldntBeExportedFn
}
以下可以在生产代码中使用:
import { exportedFn } from './myPackage';
这可以在单元测试中使用:
import { exportedFn, exportedForTesting } from './myPackage';
const { shouldntBeExportedFn } = exportedForTesting;
此策略为我团队中的其他开发人员保留了上下文线索,即除了测试之外,不应该在包之外使用
shouldntBeExportedFn()
。
我已经使用这个多年了,我发现它效果非常好。
乔丹,我希望我能为你提供更好的答案。 😊 过去我在 JavaScript 和 C# 环境中都遇到过非常类似的问题...
在某些时候,我不得不接受这样一个事实:如果我想要覆盖未导出/私有函数/方法的细粒度单元测试,我真的应该公开它们。有些人会说这是违反封装性的,但另一些人则不同意这一点。前一组人还会说,在函数被导出/公开之前,它本质上是一个实现细节,因此不应该进行单元测试。
如果你正在练习TDD,那么Mark Seeman 的解释应该是相关的(Pluralsight),并希望它能澄清为什么可以暴露事物。
我不知道您是否可以找到一些技巧来直接从单元测试中调用未导出的函数,而不需要更改被测代码,但我个人不会这样做。
另一种选择是将您的图书馆分成两部分。比如说,库
A
是您的应用程序代码,库 B
是包含您希望避免从 A
的界面导出的所有函数的包。
如果它们是两个不同的库,您可以在非常精细的水平上控制公开的内容以及如何测试它。库
A
将仅依赖于 B
,而不会泄露 B
的任何详细信息。然后,A
和 B
都可以独立测试。
这当然需要不同的代码组织,但它会起作用。 Lerna 等工具简化了 JavaScript 代码的多包存储库。
老实说,我不同意AlexSzabó。通过测试使用非导出函数的函数来测试非导出函数并不是真正的单元测试。
我知道这已经有了答案,但我不喜欢所选的答案,因为它涉及额外的功能。 :) 经过一番研究后,我找到了https://www.jstopics.com/articles/javascript-include-file(因此可以在那里找到基本想法并归功于他/她)。我的代码是 Typescript,但这也应该适用于普通的 Javascript。
假设您的源应用程序位于“app.ts”中,并且您有一个名为“function1”的私有函数:
// existing private function, no change here
function function1(s :string) :boolean => {
let result=false; /* code, result=true if good */ return result;
}
// at bottom add this new code
// exports for testing only
// make the value something that will never be in production
if (process.env['NODE_DEV'] == 'TEST') {
module.exports.function1 = function1;
module.exports.function2 = function2; // add functions as needed
}
我使用 Jest 进行单元测试,因此在tests/app.test.ts 中我们这样做:
process.env['NODE_DEV'] = 'TEST';
let app = require("../app");
describe("app tests", () => {
test('app function1', () => {
expect(app.function1("blah")).toBe(true);
expect(app.function1("foo")).toBe(false);
});
test('app function2', () => {
expect(app.function2(2)).toBeUndefined();
});
});
添加您需要的任何测试,它将测试这些私有函数。它对我有用,无需重新布线。
也许是死后发布,但我解决这个问题的方法是使用“index.js”,它仅导出您想要公开的函数。
您仍然需要导出私有函数,但这种方式确实在测试和生产之间添加了一个抽象层。
模块/startingFile.js
function privateFunction1() {/**/};
function privateFunction2() {/**/};
// Different syntax is a good visual indicator that this is different to public function
exports.privateFunction1 = privateFunction1;
exports.privateFunction2 = privateFunction2;
exports.publicFunction1 = function() {/**/};
exports.publicFunction2 = function() {/**/};
模块/index.js
exports.publicFunction1 = require('./startingFile.js').publicFunction1;
exports.publicFunction2 = require('./startingFile.js').publicFunction2;
导入File.js
const { publicFunction1, publicFunction2 } = require('./module');
您甚至可以使用 NODE_ENV 变量仅在不在生产中时导出私有函数。
对于 ES6。 如果您使用 Jest,则已为您设置“NODE_ENV”。
export let exportsForTesting;
if (process.env.NODE_ENV === 'test') {
exportsForTesting = { symbolToExport };
}
要测试私有函数,您可以在同一文件中进行内联测试。测试部分将在捆绑时被移除。一些测试框架例如Vitest允许这样做。
也许尝试有条件导出,例如:
a.js
module.exports = {
foo,
foo1: process.env['NODE_DEV'] == 'TEST123' && foo1
}
a.test.js
process.env['NODE_DEV'] = 'TEST123'
const { foo1, foo } = require('./a')
这将确保测试的功能也包含在测试覆盖范围内。
就我而言,我想为 Express 路线中的私有函数编写单元测试。
问题
如果我们这样做,测试就可以正常工作:
const exportedForTesting = {
privateFunction1,
privateFunction2,
};
module.exports = {
router,
exportedForTesting,
};
然而Express却对此抱怨:
TypeError app.use() requires a middleware function
...因为它希望看到简单的:
module.exports = router;
解决方案
我们需要将地图添加为
router
上的属性:
router.exportedForTesting = {
privateFunction1,
privateFunction2,
};
module.exports = router;
然后我们可以像Jordan的回答一样访问测试中的私有函数。
const { exportedForTesting } = require('path/to/route/file.js');
const { privateFunction1, privateFunction2 } = exportedForTesting;
如果函数未从文件中导出并且想要测试它,您可以模拟文件路径并测试子函数(未导出)。
jest.mock('../file-path')
const functions = require('../file-path')
expect(functions.childFun).toHaveBeenCalled()