Mocha 发现全局变量在扩展 class 时未定义

Mocha finds global variable is undefined when extended by class

我有来自 Chromecast 接收器的以下 ES6 模块,我想使用 Mocha 对其进行测试...

// SUT - app.js
import { Queue } from 'queue.js'
export const app = async () => {
  const context = cast.framework.CastReceiverContext.getInstance();
  const options = {};
  options.queue = new Queue();
  context.start(options);
}

此代码 运行 在 Chromecast 用户代理中,它提供对全局演员表对象的访问。这个 cast 对象公开了一个 api ,它反过来使 JS 线程能够直接与 Chromecast CAF SDK 交互。此转换变量在 运行 时间始终可用。

队列 class 有点不寻常,因为为了使其根据 CAF 框架文档工作,必须从框架 cast.framework.QueueBase 扩展抽象 class...

// queue.js
class Queue extends cast.framework.QueueBase {
  initialize(){
    // build queue here
  }
}

现在我想写一些单元测试来检查我的 app 函数是否正确。例如:

// app.test.js
import { app } from 'app.js';
it('should do some stuff', async function () {
  // inject a mock cast object
  global.cast = {
    framework: {
      QueueBase: class {},
      CastReceiverContext: {
        getInstance: () => {},
      },
    },
  };
  await app();
  // Make some assertions
});

但是,即使我正在使用 global.cast 注入模拟,这对于所有对 cast 对象的常规引用都足够了,在 class 是 扩展的情况下 注入的 cast 对象,显然它还不可用,我收到以下错误:

ReferenceError: cast is not defined

我发现了一个丑陋的 hack 来使这个错误消失。如果我将以下代码片段放在 class 声明之上,那么我可以在 运行 时间注入模拟,它不仅适用于 Mocha,也适用于在 Chromecast 设备上执行....

try {
  // The following line throws in Mocha's node environment
  // but executes fine on the Chromecast device
  if (cast) {
  }
} catch {
  global.cast = {
    framework: {
      QueueBase: class {},
    },
  };
}

export class Queue extends cast.framework.QueueBase {
...

但是,我想找到一个更好的解决方案,这样我就不必用这个 hack 污染我的生产代码,它只允许我进行 运行 测试。

我的 .mocharc.yml 文件如下所示:

require:
  - '@babel/register'
  - 'ignore-styles'
  - 'jsdom-global/register'
  - 'babel-polyfill'

... 我对 运行 测试的命令是:

mocha --recursive --use_strict

最后,我的 .babelrc 文件如下所示:

{
    "presets": [
        [
            "@babel/preset-env"
        ]
    ],
    "plugins": [
        "inline-svg",
        "import-graphql"
    ]
}

静态导入总是先求值,所以操作顺序大致是:

import { Queue } from 'queue.js'
class Queue extends cast.framework.QueueBase { // ReferenceError outside Chromecast!
  initialize(){
    // build queue here
  }
}
global.cast = {
    framework: {
      QueueBase: class {},
      CastReceiverContext: {
        getInstance: () => {},
      },
    },
  };

您可以看到在 app.js 中 cast 的引用之后 创建了 mock。

在导入应用模块之前 运行 模拟创建的唯一可靠方法是使用动态导入:

// app.test.js
it('should do some stuff', async function () {
  // inject a mock cast object
  global.cast = {
    framework: {
      QueueBase: class {},
      CastReceiverContext: {
        getInstance: () => {},
      },
    },
  };
  const { app } = await import('app.js');
  await app();
  // Make some assertions
  delete global.cast;
});

如果您不想在每个测试中重复创建模拟和 import,您可以将两者移出测试定义:

// app.test.js
// inject a mock cast object
global.cast = {
  framework: {
    QueueBase: class {},
    CastReceiverContext: {
      getInstance: () => {},
    },
  },
};
const { app } = await import('app.js');
it('should do some stuff', async function () {
  await app();
  // Make some assertions
});
// Optionally clean up the mock after all tests
after(() => delete global.cast);