我正在尝试存根从我的 ES 模块导出的函数。
我使用通配符导入(
import * as
)来存根它,并且当使用 ts-node 进行转译时它可以工作(mocha --require ts-node/register */**/*.spec.ts
)。
但是当使用swc时,它会失败并显示以下消息(
mocha --require @swc/register */**/*.spec.ts
)。
TypeError: Descriptor for property validate is non-configurable and non-writable
/* hash.ts */
import * as argon2 from 'argon2'
export async function encrypt(plain) {
return await argon2.hash(plain)
}
export async function validate(hash, plain) {
return await argon2.verify(hash, plain)
}
/* service.ts */
import { validate } from './hash'
export async function isValidUser(user: User, password: string) {
if (!user || !(await validate(user.password, password))) {
return false
}
return true
}
/* service.spec.ts */
import * as hash from './hash'
import { isValidUser } from './service'
import { stub } from 'sinon'
describe('isValidUser', () => {
stub(hash, 'validate').callsFake(
async (passwordFormDB, passwordFromUserInput) =>
passwordFromDB === passwordFromUserInput
)
it('...', async () => {
/* test `isValidUser` function */
})
})
// .swcrc
{
"test": ".*.ts$",
"jsc": {
"parser": {
"syntax": "typescript",
"decorators": true,
"importMeta": true
},
"transform": {
"legacyDecorator": true,
"decoratorMetadata": true
},
"paths": {
"src/*": ["./src/*"]
}
},
"module": {
"type": "commonjs",
"noInterop": true
}
}
由于 Sinon bug tracker 上刚刚出现了类似的问题,我决定深入研究一下是否可以解决它。事实证明这并不难,但如果您想了解背景的一些详细信息,您应该查看问题。我只是将相关部分复制粘贴到此处。
诗乃会嘲笑、存根和间谍。这些都是普通的、普通的函数,我们不会对运行时做任何魔法,所以无论要做什么都需要在给定的运行时使用普通的 Javascript 来完成。诗农清楚地说明了问题所在:
属性验证的描述符不可配置且不可写
如果转译的代码限制每个人修改这些导出,Sinon 自己就无法做任何事情。这个问题不是Sinon的问题,而是你的转译器工具的问题。
虽然
ts-node
明确导出可写对象描述符,但 SWC 却没有。这与 ES 模块的工作方式一致,因此 SWC 似乎在这里显示了正确的行为。
由于运行时不使用 ESM,而是使用 SWC 转译后的 CommonJS,因此您可以对 CommonJS 使用任何正常的 link seam 方法,基本上可以拦截模块加载。 Sinon 主页列出了一种这样的方法,即使用 Proxiquire。您还可以使用 Rewire、Quibble(TestDouble 的)或其他基本上具有相同功能的工具。
describe('isValidUser', () => {
const mySpy = sinon.spy(async (passwordFormDB, passwordFromUserInput) =>
passwordFromDB === passwordFromUserInput)
quibble('./hash', { validate: mySpy });
it('...', async () => {
/* test `isValidUser` function */
})
})
链接的问题还展示了如果您不使用 TypeScript,而是运行未转译的 ESM,则如何通过使用 Quibble 作为模块加载器来替换模块。它还解释了为什么当前使用 TypeScript 定位 ES 模块 (
"moduleResolution": "Node16"
) 时很难(并非不可能)模拟模块,因为您需要编写一个委托加载器来处理文件名解析,然后再移交给 Quibble。
ts-node 在 typescript 3.8 之后也不起作用