简体   繁体   English

如何存根require()/ expect调用模块的“root”函数?

[英]How to stub require() / expect calls to the “root” function of a module?

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(). 一个理论解决方案是替换require()并返回使用createSpy()创建的间谍。 BUT require() is an unstoppable beast: it is a different "copy" of the function in each and every source file/module. 但是require()是一个不可阻挡的野兽:它是每个源文件/模块中函数的不同“副本”。 Stubbing it in the spec won't replace the real require() function in the "testee" source file. 在规范中对它进行存根不会取代“testee”源文件中的真实require()函数。

An alternative is to add some fake modules to the load path, but it looks too complicated to me. 另一种方法是在加载路径中添加一些假模块,但它看起来太复杂了。

Any idea? 任何的想法?

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);
  });
});

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. 它甚至没有考虑到被测试者的源代码,这对我来说是一种标准。

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();

This was very helpful, but it doesn't support calling through via .andCallThrough() . 这非常有用,但它不支持通过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;
};

You can use gently module (https://github.com/felixge/node-gently). 您可以轻轻使用模块(https://github.com/felixge/node-gently)。 Hijacking require is mentioned in examples, and dirty NPM module actively uses it, so I suppose it works. 在示例中提到了劫持要求,并且脏NPM模块主动使用它,因此我认为它有效。

There is another approach. 还有另一种方法。 You can put the module in the global scope by not using var when requiring it: 您可以在需要时不使用var将模块放在全局范围中:

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. 然后显然要确保无论someFunctionThatShouldCallTheModule是什么,你需要包装而不是真正的模块。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM