如何存根 require() / Expect 对“root”的调用模块的功能?

发布于 2024-11-28 10:17:27 字数 1163 浏览 4 评论 0原文

考虑以下茉莉花规范:

describe("something.act()", function() {
  it("calls some function of my module", function() {
    var mod = require('my_module');
    spyOn(mod, "someFunction");
    something.act();
    expect(mod.someFunction).toHaveBeenCalled();
  });
});

这工作得很好。像这样的东西使它变得绿色:

something.act = function() { require('my_module').someFunction(); };

现在看看这个:

describe("something.act()", function() {
  it("calls the 'root' function of my module", function() {
    var mod = require('my_module');
    spyOn(mod); // jasmine needs a property name
                // pointing to a function as param #2
                // therefore, this call is not correct.
    something.act();
    expect(mod).toHaveBeenCalled(); // mod should be a spy
  });
});

这是我想用这个规范测试的代码:

something.act = function() { require('my_module')(); };

在过去的几个月里,这让我陷入了困境。一种理论上的解决方案是替换 require() 并返回使用 createSpy() 创建的间谍。但是 require() 是一个不可阻挡的野兽:它是每个源文件/模块中函数的不同“副本”。在规范中存根它不会替换“testee”源文件中真正的 require() 函数。

另一种方法是在加载路径中添加一些假模块,但对我来说它看起来太复杂了。

有什么想法吗?

Consider the following jasmine spec:

describe("something.act()", function() {
  it("calls some function of my module", function() {
    var mod = require('my_module');
    spyOn(mod, "someFunction");
    something.act();
    expect(mod.someFunction).toHaveBeenCalled();
  });
});

This is working perfectly fine. Something like this makes it green:

something.act = function() { require('my_module').someFunction(); };

Now have a look at this one:

describe("something.act()", function() {
  it("calls the 'root' function of my module", function() {
    var mod = require('my_module');
    spyOn(mod); // jasmine needs a property name
                // pointing to a function as param #2
                // therefore, this call is not correct.
    something.act();
    expect(mod).toHaveBeenCalled(); // mod should be a spy
  });
});

This is the code I'd like to test with this spec:

something.act = function() { require('my_module')(); };

This has bogged me down several times in the last few months. One theoretical solution would be to replace require() and return a spy created with createSpy(). BUT require() is an unstoppable beast: it is a different "copy" of the function in each and every source file/module. Stubbing it in the spec won't replace the real require() function in the "testee" source file.

An alternative is to add some fake modules to the load path, but it looks too complicated to me.

Any idea?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

并安 2024-12-05 10:17:28

rewire 对此非常棒

var rewire = require('rewire');

describe("something.act()", function() {
  it("calls the 'root' function of my module", function() {
    var mod = rewire('my_module');
    var mockRootFunction = jasmine.createSpy('mockRootFunction');
    var requireSpy = {
      mockRequire: function() {
        return mockRootFunction;
      }
    };
    spyOn(requireSpy, 'mockRequire').andCallThrough();

    origRequire = mod.__get__('require');
    mod.__set__('require', requireSpy.mockRequire);

    something.act();
    expect(requireSpy.mockRequire).toHaveBeenCalledWith('my_module');
    expect(mockRootFunction).toHaveBeenCalled();

    mod.__set__('require', origRequire);
  });
});

rewire is awesome for this

var rewire = require('rewire');

describe("something.act()", function() {
  it("calls the 'root' function of my module", function() {
    var mod = rewire('my_module');
    var mockRootFunction = jasmine.createSpy('mockRootFunction');
    var requireSpy = {
      mockRequire: function() {
        return mockRootFunction;
      }
    };
    spyOn(requireSpy, 'mockRequire').andCallThrough();

    origRequire = mod.__get__('require');
    mod.__set__('require', requireSpy.mockRequire);

    something.act();
    expect(requireSpy.mockRequire).toHaveBeenCalledWith('my_module');
    expect(mockRootFunction).toHaveBeenCalled();

    mod.__set__('require', origRequire);
  });
});
灵芸 2024-12-05 10:17:28

看起来我找到了一个可以接受的解决方案。

规范助手:

var moduleSpies = {};
var originalJsLoader = require.extensions['.js'];

spyOnModule = function spyOnModule(module) {
  var path          = require.resolve(module);
  var spy           = createSpy("spy on module \"" + module + "\"");
  moduleSpies[path] = spy;
  delete require.cache[path];
  return spy;
};

require.extensions['.js'] = function (obj, path) {
  if (moduleSpies[path])
    obj.exports = moduleSpies[path];
  else
    return originalJsLoader(obj, path);
}

afterEach(function() {
  for (var path in moduleSpies) {
    delete moduleSpies[path];
  }
});

规范:

describe("something.act()", function() {
  it("calls the 'root' function of my module", function() {
    var mod = spyOnModule('my_module');
    something.act();
    expect(mod).toHaveBeenCalled(); // mod is a spy
  });
});

这并不完美,但做得很好。它甚至不会与测试者源代码混淆,这对我来说是一种标准。

It looks like I found an acceptable solution.

The spec helper:

var moduleSpies = {};
var originalJsLoader = require.extensions['.js'];

spyOnModule = function spyOnModule(module) {
  var path          = require.resolve(module);
  var spy           = createSpy("spy on module \"" + module + "\"");
  moduleSpies[path] = spy;
  delete require.cache[path];
  return spy;
};

require.extensions['.js'] = function (obj, path) {
  if (moduleSpies[path])
    obj.exports = moduleSpies[path];
  else
    return originalJsLoader(obj, path);
}

afterEach(function() {
  for (var path in moduleSpies) {
    delete moduleSpies[path];
  }
});

The spec:

describe("something.act()", function() {
  it("calls the 'root' function of my module", function() {
    var mod = spyOnModule('my_module');
    something.act();
    expect(mod).toHaveBeenCalled(); // mod is a spy
  });
});

This is not perfect but does the job quite well. It does not even mess with the testee source code, which is kind of a criterion for me.

一身仙ぐ女味 2024-12-05 10:17:28

我今天需要这样做并发现了这篇文章。我的解决方案如下:

在规范助手中:

var originalRequire = require;
var requireOverrides = {};

stubModule = function(name) {
  var double = originalRequire(name);
  double['double'] = name;
  requireOverrides[name] = double;
  return double;
}

require = function(name) {
  if (requireOverrides[name]) {
    return requireOverrides[name];
  } else {
    return originalRequire(name);
  }
}

afterEach(function() {
  requireOverrides = {};
});

在规范中:

AWS = stubModule('aws-sdk');
spyOn(AWS.S3, 'Client');

// do something

expect(AWS.S3.Client).toHaveBeenCalled();

I needed to do this today and came across this post. My solution follows:

In a spec helper:

var originalRequire = require;
var requireOverrides = {};

stubModule = function(name) {
  var double = originalRequire(name);
  double['double'] = name;
  requireOverrides[name] = double;
  return double;
}

require = function(name) {
  if (requireOverrides[name]) {
    return requireOverrides[name];
  } else {
    return originalRequire(name);
  }
}

afterEach(function() {
  requireOverrides = {};
});

In a spec:

AWS = stubModule('aws-sdk');
spyOn(AWS.S3, 'Client');

// do something

expect(AWS.S3.Client).toHaveBeenCalled();
ゃ人海孤独症 2024-12-05 10:17:28

这非常有帮助,但它不支持通过 .andCallThrough() 进行调用。

不过我能够适应它,所以我想我会分享:

function clone(obj) {
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }
  var key;
  var temp = new obj.constructor();
  for (key in obj) {
    if (obj.hasOwnProperty(key)) {
      temp[key] = clone(obj[key]);
    }
  }
  return temp;
};

spyOnModule = function spyOnModule(name) {
  var path          = require.resolve(name);
  var spy           = createSpy("spy on module \"" + name + "\"");
  moduleSpies[path] = spy;

  // Fake calling through
  spy.andCallThrough = function() {

    // Create a module object
    var mod = clone(module);
    mod.parent = module;
    mod.id = path;
    mod.filename = path;

    // Load it backdoor
    originalJsLoader(mod, path);

    // And set it's export as a faked call
    return this.andCallFake(mod.exports);
  }

  delete require.cache[path];
  return spy;
};

This was very helpful, but it doesn't support calling through via .andCallThrough().

I was able to adapt it though, so I thought I'd share:

function clone(obj) {
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }
  var key;
  var temp = new obj.constructor();
  for (key in obj) {
    if (obj.hasOwnProperty(key)) {
      temp[key] = clone(obj[key]);
    }
  }
  return temp;
};

spyOnModule = function spyOnModule(name) {
  var path          = require.resolve(name);
  var spy           = createSpy("spy on module \"" + name + "\"");
  moduleSpies[path] = spy;

  // Fake calling through
  spy.andCallThrough = function() {

    // Create a module object
    var mod = clone(module);
    mod.parent = module;
    mod.id = path;
    mod.filename = path;

    // Load it backdoor
    originalJsLoader(mod, path);

    // And set it's export as a faked call
    return this.andCallFake(mod.exports);
  }

  delete require.cache[path];
  return spy;
};
神妖 2024-12-05 10:17:28

您可以使用gently模块(https://github.com/felixge/node-gently)。示例中提到了劫持 require,并且肮脏的 NPM 模块积极使用它,所以我认为它是有效的。

You can use gently module (https://github.com/felixge/node-gently). Hijacking require is mentioned in examples, and dirty NPM module actively uses it, so I suppose it works.

土豪我们做朋友吧 2024-12-05 10:17:28

还有另一种方法。您可以在需要时不使用 var 将模块放入全局范围:

someModule = require('someModule');

describe('whatever', function() {
  it('does something', function() {
    spyOn(global, 'someModule');

    someFunctionThatShouldCallTheModule();

    expect(someModule).toHaveBeenCalled();
  }
}

您也可以将模块包装在另一个模块中:

//someModuleWrapper.js
require('someModule');

function callModule(arg) {
  someModule(arg);
}
exports.callModule = callModule;

//In the spec file:
someModuleWrapper = require('someModuleWrapper');

describe('whatever', function() {
  it('does something', function() {
    spyOn(someModuleWrapper, 'callModule');

    someFunctionThatShouldCallTheModule();

    expect(someModuleWrapper.callModule).toHaveBeenCalled();
  }
}

然后显然确保无论 someFunctionThatShouldCallTheModule 是,您需要包装器而不是真正的模块。

There is another approach. You can put the module in the global scope by not using var when requiring it:

someModule = require('someModule');

describe('whatever', function() {
  it('does something', function() {
    spyOn(global, 'someModule');

    someFunctionThatShouldCallTheModule();

    expect(someModule).toHaveBeenCalled();
  }
}

You could also wrap the module in another module:

//someModuleWrapper.js
require('someModule');

function callModule(arg) {
  someModule(arg);
}
exports.callModule = callModule;

//In the spec file:
someModuleWrapper = require('someModuleWrapper');

describe('whatever', function() {
  it('does something', function() {
    spyOn(someModuleWrapper, 'callModule');

    someFunctionThatShouldCallTheModule();

    expect(someModuleWrapper.callModule).toHaveBeenCalled();
  }
}

And then obviously make sure that wherever someFunctionThatShouldCallTheModule is, you're requiring the wrapper rather than the real module.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文