在mocha测试之间重新导入模块

时间:2018-04-24 13:17:34

标签: node.js typescript mocha

在我的node / typescript express app中,我将配置设置存储在settings.json文件中,该文件由config.ts加载并导出为对象。每个使用配置设置的模块都会像这样导入模块:

import Config from './config';

config.ts看起来像这样(本例简化):

class Config {
  public static get(): any {
    const settings = require('settings.json');
    return settings;
  }
}

export default Config.get();

当应用程序运行时,这一切都正常。但是我的mocha测试有问题。在某些测试中,我想在触发应用程序功能之前更改配置设置(例如Config.someSetting = 'someValue'),然后在运行下一个测试之前将配置设置重置为默认值。

我知道我可以手动将每个更改的配置值重置为默认值,但理想情况下我想“重新导入”config.ts模块,该模块会将所有配置设置重置为默认值。我的问题是,最好的方法是什么?

我尝试使用decache并将以下内容添加到afterEach

decache('./config');

即使我可以看到config.ts不再位于require缓存中,但Config对象仍然存在,并且所有后续测试的当前值都不存在(config.ts未被“重新导入” )。

我做错了什么?

2 个答案:

答案 0 :(得分:1)

如果在decache之后重新评估require('settings.json'),即调用decache('settings.json'),则Config.get()等缓存修改程序包应该适用于此情况。

由于修改了settings.json模块对象,因此应该将其恢复。 decache应直接影响应缓存的包,即settings.json。如果Config.get()未被多次调用,则./config'以及导入它的每个模块也应该被缓存。这使得在这种情况下使用decache是不合理的。

这里的问题是配置模块不是测试友好的。仅静态类是反模式的。如果代码显示未导出Config,那么这也是反模式,因为它提供的抽象不能在模块导出时多次使用。

为了改善这种情况,配置模块应该以一种方式重构,它可以在导入后使用配置对象的模块中重新评估require('settings.json')

export default function getConfig() {
  return require('settings.json');
}

getConfig()应始终按原样使用,不应在使用它的模块顶部分配const config = getConfig(),这将使其无法缓存。

目前恢复原始配置的一种方法是在保持对现有对象的引用的同时修改它,例如:

afterEach(() => {
  decache('./settings.json');
  Object.assign(Config, require('./settings.json'));
});

可以看出。 Config.get抽象没有任何帮助。

转换 ES模块的另一种方法是直接修补模块对象。由于模块对象应该是根据规范的导出的只读反射。预计模块将通过转换器进行相应处理,包括TypeScript。这取决于应用程序的构建方式,并且可能无法在任何环境中按预期工作。

import Config from './config';
console.log(Config.foo);

应该转换为类似

的内容
Object.defineProperty(exports, "__esModule", { value: true });    
console.log(config_1.default.foo;);

这可能允许动态地破坏ES模块导出(对于CommonJS模块默认导出不可能)并影响那些使用Config并重新评估的模块部分(例如内部函数但不在顶级模块范围内) :

afterEach(() => {
  decache('./settings.json');
  const configModule = require('./config'));
  configModule.default = require('./settings.json');
});

答案 1 :(得分:0)

我发现的最佳方法是使用 proxyquire

const proxyquire = require('proxyquire');

let moduleUnderTest;

describe('Given a Service Provider', () => {
    beforeEach(() => {
        proxyquire.noPreserveCache(); // Tells proxyquire to not fetch the module from cache

        // Ensures that each test has a freshly loaded instance of moduleUnderTest
        moduleUnderTest = proxyquire(
            '../../../../src/data/firebase/admin/service-provider',
            {} // Stub if you need to, or keep the object empty
        );
    });

    // Use moduleUnderTest as you like
});