【问题标题】:how to reset a requirejs module between unit tests如何在单元测试之间重置 requirejs 模块
【发布时间】:2026-01-27 18:50:01
【问题描述】:

我有一个 JavaScript 项目,我想观察 TDD 方法。我为此选择了 karma 框架和 requirejs 库,并按照 karma 文档here 中演示的示例进行操作。

有一个单元测试文件的示例,即:

define(['app', 'jquery', 'underscore'], function(App, $, _) {

    describe('just checking', function() {

        it('works for app', function() {
            var el = $('<div></div>');

            var app = new App(el);
            app.render();

            expect(el.text()).toEqual('require.js up and running');
        });

        it('works for underscore', function() {
            // just checking that _ works
            expect(_.size([1,2,3])).toEqual(3);
        });

    });

});

这种方法的主要问题是没有办法为每个测试清除 App 模块。因此,如果在一个it 调用中应用程序发生了一些变化(如闭包变量更改等),那么它们可能会影响其他it 调用。

有人可以就此提出建议吗?这些都是如此流行的工具,我不敢相信没有人遇到过这种情况。

所以,为了概括一下这个问题,我想为这些更具体的问题寻求答案:

  • 有什么方法可以“重置”(清除)requirejs 中的模块吗? (其实我想没有这样的办法,除了重新加载所有模块一次
  • 是否有更好的方法来运行 karma 和 requirejs,以便模块上没有其他测试(it 函数调用)的任何残留(副作用)?

【问题讨论】:

    标签: javascript unit-testing requirejs karma-runner


    【解决方案1】:

    在 Google 的帮助下,我找到了解决方案。我不能说它是理想的,但它至少可以工作,并且在每个测试中,所需的模块都处于 原始 状态。

    首先,他的main.js 测试文件需要像这样,它与docs 中的几乎相同,除了测试未定义为模块并且不包含为依赖项:

    require.config({file
        baseUrl: '/base',
        paths: {},
        shim: {},
        deps: [],
        callback: window.__karma__.start
    });
    

    在主业力配置中必须存在这个(需要调整自己的路径)

    files: [
        {pattern: 'src/**/*.js', included: false},
        'tests/unit/**/*.js'
    ],
    

    在测试本身中,我们利用 jasmine 异步测试功能和 requirejs.undef 方法“清除”模块(将重置加载器的内部状态忘记模块的先前定义,正如docs 中所述)。

    这个方法有一个含义,它不会重置其他已经加载并依赖于重置模块的模块,但这应该不是问题,如果一个人以正确的方式编写他的测试,那就是他测试只有一个模块(并且可能是一些父模块,子模块从中继承了一些行为),因此在一个模块中undef 几个模块应该不难。

    describe('Some/Module tests', function() {
        'use strict'
        var M
    
        beforeEach(function(done) {
            requirejs.undef('src/Some/GParentModule')
            requirejs.undef('src/Some/ParentModule')
            requirejs.undef('src/Some/Module')
            require(['src/Some/Module'], function(m) {
                M = m
                done()
            })
        })
    
        it('some description', function() {
            var i = 0
    
            for (var prop in M) {
                i++
            }
            expect(i).toEqual(1)
            expect(prop).toEqual('init')
            expect(M.init).toEqual(jasmine.any(Function))
        })
    })
    

    我已经检查过了,在it 调用之间对模块的更改不会持续存在,所以我想,使用这种方法是安全的。

    感谢大家的帮助,如果有人有更好的方法,请在这里回答。

    【讨论】:

      【解决方案2】:
      • 有没有办法在 requirejs 中“重置”(清除)模块?

      您可以使用requirejs.undef(),但文档中提到了一些问题。据我所知,没有任何调用可以用来说“卸载此模块及其依赖的所有内容”,这在复杂的应用程序中可能是需要的。

      • 有没有更好的方法来运行 karma 和 requirejs,以便模块上没有其他测试(它的函数调用)的任何残留(副作用)?

      更好的方法是设计您的应用程序,使状态与您实例化的对象相关联,而不是与模块 本身相关联。这就是我在我的一个大型应用程序的测试套件中所做的。 beforeEach 挂钩重置需要重置的内容,并为每次测试重新实例化应用程序。

      【讨论】:

      • 谢谢,但我不同意您的建议设计您的应用程序,以便将状态绑定到您实例化的对象而不是模块本身,因为即使我这样做(尽管我不想这样做),问题仍然存在,至少在 JavaScript 中。由于模块定义是一个函数这一事实,即使我实例化一个对象,我也不能将它作为 clear 用于每个测试(至少可以更改一些闭包变量)。
      • 看来我找到了一个可以接受的解决方案,我将在下面发布作为答案
      • @user907860 你一定对 JavaScript 的工作原理有些误解,因为 它是完全可行的。正如我在回答中所说,我已经做到了。
      • 能否提供一些代码示例?我的意思是,如果您在许多 it 调用中测试的模块只有一个 require/define 调用,那么您无法确定(仅当您不侵入模块的内部内容时)它的状态被重置。因为,如果我在模块实例上调用一个方法,它是这样的:define(function() {var foo; return function() {this.method = function() {foo++}}})。然后,如果我在一个测试中调用var ins = new Module; ins.method(),我将在另一个测试中使用foo += 1 这个模块。
      • 即使在其他测试中(或在 beforeEach 中)我调用 var ins_2 = new Module 我仍然会有 Module 其内部 foo 变量递增 1