NodeJS 从本机ES模块(不带babel)存根导出

vjrehmav  于 2023-02-03  发布在  Node.js
关注(0)|答案(3)|浏览(125)

我正在使用AVA + sinon来构建我的单元测试。因为我需要ES6模块,而且我不喜欢babel,所以我在整个项目中使用mjs文件,包括测试文件。我使用“--experimental-modules”参数来启动我的项目,并在测试中使用“esm”包。以下是我的ava配置和测试代码。

"ava": {
    "require": [
      "esm"
    ],
    "babel": false,
    "extensions": [
      "mjs"
    ]
  },

// test.mjs
import test from 'ava';
import sinon from 'sinon';
import { receiver } from '../src/receiver';
import * as factory from '../src/factory';

test('pipeline get called', async t => {
  const stub_factory = sinon.stub(factory, 'backbone_factory');
  t.pass();
});

但我收到错误消息:

TypeError {
    message: 'ES Modules cannot be stubbed',
  }

我怎样才能在没有巴别塔的情况下使ES6模块失效?

ymzxtsji

ymzxtsji1#

根据esm package的创建者John-David道尔顿的说法,只能改变*.js文件的命名空间-*.mjs文件被“锁定”。
这意味着Sinon(以及所有其他软件)无法存根这些模块-正如错误消息所指出的。有两种方法可以解决这个问题:
1.只需将文件的扩展名重命名为.js,以使导出的文件可变,这是最小的侵入性,因为mutableNamespace选项在esm中默认为 on,当然,这仅适用于使用esm加载程序时。
1.使用一个专用的模块加载器来代理所有的导入,并用您喜欢的一个来替换它们。
选项2的技术栈不可知术语是link seam--基本上取代了Node的默认模块加载器。通常可以使用QuibbleESMockproxyquirerewire,这意味着使用Proxyquire时上面的测试看起来像这样:

// assuming that `receiver` uses `factory` internally

// comment out the import - we'll use proxyquire
// import * as factory from '../src/factory';
// import { receiver } from '../src/receiver';

const factory = { backbone_factory: sinon.stub() };
const receiver = proxyquire('../src/receiver', { './factory' : factory });

修改proxyquire示例以使用Quibble或ESMock(两者都原生支持ESM)应该很简单。

mqxuamgl

mqxuamgl2#

Sinon需要与时俱进,否则就会落后(ESM is becoming defacto now with Node 12),因为由于其许多限制,使用它将是一个巨大的痛苦。
This article提供了一个变通方法(实际上是4个,但我只发现1个是可以接受的)。在我的例子中,我直接从模块中导出函数,并得到以下错误:ES模块无法存根化

export function abc() {

}

解决方案是将函数放入一个类中,然后将其导出:

export class Utils {
  abc() {
  
  }
}

注意,在方法语法中删除了function关键字。
快乐编码-希望西农使它在长期运行,但它看起来不太好,因为它过于僵化。

zujrkrfu

zujrkrfu3#

坚持提问Headline**Stub a export from a native ES Module without babel "**这是我的看法,使用摩卡和esmock:
(致谢:当然@oligofren把我带到了正确的道路上......)

    • 包. json:**
"scripts": {
      ...
      "test": "mocha --loader=esmock",

  "devDependencies": {
      "esmock": "^2.1.0",
      "mocha": "^10.2.0",
    • 测试爸爸. js**(一个类)
import { sonBar } from './testSon.js'

export default class TestDad {
  constructor() {
    console.log(purple('constructing TestDad, calling...'))
    sonBar()
  }
}
    • testSon. js**(一个"实用程序"库)
export const sonFoo = () => {
  console.log(`Original Son 'foo' and here, my brother... `)
  sonBar()
}

export const sonBar = () => {
  console.log(`Original Son bar`)
}

export default { sonFoo, sonBar }
    • 模拟测试. js**
import esmock from 'esmock'

describe.only(autoSuiteName(import.meta.url),
  () => {
    it('Test 1', async() => {
      const TestDad = await esmock('../src/commands/TestDad.js', {
        '../src/commands/testSon.js': {
          sonBar: () => { console.log('STEPSON Bar') }
        }
      })

      // eslint-disable-next-line no-new
      new TestDad()
    })
    it('Test 2', async() => {
      const testSon = await esmock('../src/commands/testSon.js')

      testSon.sonBar = () => { console.log('ANOTHER STEPSON Bar') }

      testSon.sonFoo() // still original
      testSon.sonBar() // different now
    })
  })
autoSuiteName(import.meta.url)

关于测试1

  • 工作良好,进口弯曲如预期。

关于测试1

  • 弯曲一个函数来做其他事情不是问题。(但是调用您自己刚刚定义的函数并没有太多的测试价值,是吗?)
  • 模块内的封闭函数调用(即从sonFoosonBar)保持原样,它们实际上是closure,仍然指向前一个函数
  • Btw还测试了:没有更好的结果与sinon. callsfake()(本来会令人惊讶,如果有...)

相关问题