我喜欢使用 Javascript 作为 bash 脚本的替代品。
假设有一个名为 start.js 的脚本,使用
node start.js
: 运行
const shelljs = require("shelljs")
if (!shelljs.which("serve")) {
shelljs.echo("'serve' is missing, please run 'npm ci'")
process.exit(1)
}
shelljs.exec("serve -s build -l 3000")
我如何测试:
serve
不可用,则永远不会调用 serve -s build -l 3000
并且程序会以代码 1 退出。serve
可用,则调用 serve -s build -l 3000
。我不介意嘲笑“shelljs”,
process.exit
或其他任何东西。
我的主要问题是弄清楚如何在测试套件中
require
或import
这样一个无功能和无模块的文件,并让它在存在模拟的每个单独测试中运行一次没有实际上将其转换为CommonJS/ ES6 模块。
只需模拟
shelljs
模块,并监视 process.exit
功能。
describe("start.js", () => {
let shelljs
let exitSpy
beforeEach(() => {
jest.mock("shelljs", () => {
return {
exec: jest.fn(),
which: jest.fn(),
echo: jest.fn(),
}
})
shelljs = require("shelljs")
exitSpy = jest.spyOn(process, "exit").mockImplementation(() => {})
});
afterEach(() => {
jest.resetModules()
jest.resetAllMocks()
})
it("should execute process.exit with code is 1 when 'serve' is not existed", () => {
shelljs.which.mockReturnValue(false)
require("./start")
expect(shelljs.which).toHaveBeenCalledWith("serve");
expect(shelljs.echo).toHaveBeenCalledWith("'serve' is missing, please run 'npm ci'")
expect(exitSpy).toHaveBeenCalledWith(1)
// expect(shelljs.exec).toHaveBeenCalled() // can not check like that, exitSpy will not "break" your code, it will be work well if you use if/else syntax
});
it("should execute serve when 'serve' is existed", () => {
shelljs.which.mockReturnValue(true)
require("./start")
expect(shelljs.which).toHaveBeenCalledWith("serve");
expect(shelljs.echo).not.toHaveBeenCalled()
expect(exitSpy).not.toHaveBeenCalled()
expect(shelljs.exec).toHaveBeenCalledWith("serve -s build -l 3000")
});
})
另一种确保生产代码在调用
process.exit
时被破坏的方法。模拟 exit
函数抛出错误,然后期望 shelljs.exec
不会被调用
describe("start.js", () => {
let shelljs
let exitSpy
beforeEach(() => {
jest.mock("shelljs", () => {
return {
exec: jest.fn(),
which: jest.fn(),
echo: jest.fn(),
}
})
shelljs = require("shelljs")
exitSpy = jest.spyOn(process, "exit").mockImplementation(() => {
throw new Error("Mock");
})
});
afterEach(() => {
jest.resetModules()
jest.resetAllMocks()
})
it("should execute process.exit with code is 1 when 'serve' is not existed", () => {
shelljs.which.mockReturnValue(false)
expect.assertions(5)
try {
require("./start")
} catch (error) {
expect(error.message).toEqual("Mock")
}
expect(shelljs.which).toHaveBeenCalledWith("serve");
expect(shelljs.echo).toHaveBeenCalledWith("'serve' is missing, please run 'npm ci'")
expect(exitSpy).toHaveBeenCalledWith(1)
expect(shelljs.exec).not.toHaveBeenCalled()
});
it("should execute serve when 'serve' is existed", () => {
shelljs.which.mockReturnValue(true)
require("./start")
expect(shelljs.which).toHaveBeenCalledWith("serve");
expect(shelljs.echo).not.toHaveBeenCalled()
expect(exitSpy).not.toHaveBeenCalled()
expect(shelljs.exec).toHaveBeenCalledWith("serve -s build -l 3000")
});
})
另一种方法是将脚本提取到一个函数中,将该函数添加到另一个文件中,然后正确测试另一个文件。
所以基本上,创建一个
helper.js
:
const shelljs = require("shelljs")
function helper() {
if (!shelljs.which("serve")) {
shelljs.echo("'serve' is missing, please run 'npm ci'")
process.exit(1)
}
shelljs.exec("serve -s build -l 3000")
}
module.exports = helper;
然后在
start.js
:
const helper = require('./helper');
helper();
现在可以正常测试
helper.js
了,我觉得不测试也没关系start.js
有类似问题。
想要对基于浏览器的文件进行单元测试,这样我就不必修改原始的 JavaScript。我解决这个问题的方法是将要测试的脚本包装在一个新函数中,提供一种从脚本中导出内容的方法(以便可以在单元测试中测试它们),以及一种向脚本提供一些内容(例如依赖项)的方法(否则会导致测试运行时抛出错误,例如 JQuery 或 DOM 对象等第 3 方库)。
代码位于 npm 包中并公开,因为这个问题被问了几次:
https://www.npmjs.com/package/@moikaturns/import-pob-code
因此,如果使用 Node,则可以导入包并与 Jest 或 Mocha 等测试平台一起使用,或者从 git 复制代码并适应特定需求。这只是几行。没什么复杂的。