在对显示模块进行单元测试时如何存根私有函数
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 忽略任何类型并假设你可以做到。
我一直在构建一个节点模块,它包装了对 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 忽略任何类型并假设你可以做到。