在对显示模块进行单元测试时如何存根私有函数

How do you stub private functions when unit testing a revealing module

我一直在构建一个节点模块,它包装了对 GitHub API 的多次调用,并且以我无限的智慧使用揭示模块模式构建了它,使我的包装函数保持私有并且只公开简单的方法。请参阅以下示例:

github.shortcuts = (function(){

  var appPath;

  var createRepo = function(name){
    var deferred = Q.defer();
    github.repos.create({
      name: name,
      auto_init: true
    }, function(error, result){
      if (error) {
        deferred.reject(new Error(error));
      } else {
        deferred.resolve(result);
      }
    });
    return deferred.promise;
  };

  var updateRef = function(result){
    var deferred = Q.defer();
    var user = result.user;
    var repo = result.repo;
    github.gitdata.updateReference({
      user: user,
      repo: repo,
      ref: 'heads/master',
      sha: result.sha
    }, function(error, result){
      if (error) {
        deferred.reject(new Error(error));
      } else {
        deferred.resolve(result);
      }
    });
    return deferred.promise;
  };

  return {
    init: function(token, name, path){
      var deferred = Q.defer();
      appPath = path;

      var error = function(error){
        return deferred.reject(error);
      };

      github.authenticate({
        type: "oauth",
        token: token
      });

      createRepo(name)
        .then(updateRef, error)
        .then(function(result){
          deferred.resolve(result);
        }, error);

      return deferred.promise;
    }
  };

})();

然而为此编写单元测试给我带来了问题。我不想测试我的私有函数,只是 public 一个 init(),但是我想对私有函数进行存根,这样测试就不会调用 GitHub API。我使用 Mocha 和 Chai 进行测试,使用 Sinon 进行 spys/stubs。

任何关于如何存根这些函数的建议,或者如果这是一个错误的模式,我将不胜感激!

由于 github 看起来像一个单例,您可以覆盖它的功能:

github.gitdata.updateReference = sinon.stub().return(Promise.resolve([]));

并且您必须在测试完成后重置它:

afterAll(() => {
 github.gitdata.updateReference.reset();
});

您应该隔离 class 中有问题的部分并将其模拟出来,而不是存根私有方法。模拟或存根私有方法的需要是一种设计味道,IMO,表明 class 太大,您应该将各种关注点分开。

在这种情况下,您可以将 github API 作为参数传递给 init,而不是挖掘 class 的内部结构,只需提供returns 静态结果的假 API。

这在您开始测试错误情况时特别有用,因为您的模拟 API 可以抛出适当的错误并允许您仅测试错误处理行为。

实际上,您有很多非常正当的理由希望将私有函数分出。好像里面有很多逻辑,你只想测试那个逻辑。要像处理任何其他函数一样将任何私有函数存根,然后将 as any 附加到父对象:

Spec.ts:

startTimeoutTimerSpy = spyOn(service as any, 'startTimeoutTimer');

服务有一个私有函数startTimeoutTimer。将 as any 附加到服务对象会告诉 TypeScript 忽略任何类型并假设你可以做到。