簡體   English   中英

如何存根require()/ expect調用模塊的“root”函數?

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

請考慮以下茉莉花規格:

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()函數。

另一種方法是在加載路徑中添加一些假模塊,但它看起來太復雜了。

任何的想法?

重新布線非常棒

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

看起來我找到了一個可接受的解決方案。

規范助手:

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

這並不完美,但工作做得很好。 它甚至沒有考慮到被測試者的源代碼,這對我來說是一種標准。

我今天需要這樣做,並發現了這篇文章。 我的解決方案是:

在規范助手中:

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

這非常有用,但它不支持通過via .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;
};

您可以輕輕使用模塊(https://github.com/felixge/node-gently)。 在示例中提到了劫持要求,並且臟NPM模塊主動使用它,因此我認為它有效。

還有另一種方法。 您可以在需要時不使用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是什么,你需要包裝而不是真正的模塊。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM