单元测试功能,如何实现对mock对象的调用
Unit testing function, how to implement call to mock object
我正在为控制器编写单元测试,该控制器具有 angular 作用域函数,调用时会调用 parse.com 框架函数,调用解析服务器和 returns 成功参数或错误代码。
如何为单元测试模拟该对象?
控制器范围内的函数名称是Parse.User.logIn,这是我到目前为止为它制作的模拟对象。
mockParse = {
User: {
logIn: function(_userName, _password, callbackObj) {
callbackObj.success({
attributes:{
username: "testuser",
email: "testuser@example.com"
}
$rootScope.user = _user;
$rootScope.isLoggedIn = true;
$state.go('tab.home');
};
callbackObj.error({
err:{
code: 101
}
if (err.code == 101){
$scope.error.message = "invalid login credentials";
} else {
$scope.error.message = "Unexpected error"
}
});
}
}
}
我做对了吗?我是否必须为不同的回调响应、错误和成功创建不同的对象?
当我把它放在测试中时,我在哪里注入它?我是否像这样将它放在控制器中?:
ctrl = $controller("LoginCtrl", {
$scope: $scope,
Parse: mockParse
});
这是实际函数:
Parse.User.logIn(($scope.user.username) , $scope.user.password, {
success: function(_user) {
console.log('Login Success');
console.log('user = ' + _user.attributes.username);
console.log('email = ' + _user.attributes.email);
$ionicLoading.hide();
$rootScope.user = _user;
$rootScope.isLoggedIn = true;
$state.go('tab.home');
},
error: function(user, err) {
$ionicLoading.hide();
// The login failed. Check error to see why.
if (err.code === 101) {
$scope.error.message = 'Invalid login credentials';
} else {
$scope.error.message = 'An unexpected error has ' +
'occurred, please try again.';
}
console.log('error message on Parse.User.logIn: ' + $scope.error.message);
$scope.$apply();
}
});
前言:我不太精通Parse.js(也不是茉莉花)
我建议您去获取 sinonjs - 超级有效并且(一段时间后)易于使用 mocking/stubbing/spying。
首先不要担心 Parse
对象的内部实现,我们只对单元测试设置中的依赖项 input
和 output
感兴趣。应该没有理由在那个精细的等级细节中剔除服务对象。
beforeEach
var $scope, Parse, createController;
beforeEach(function () {
Parse = {};
module('your_module', function ($provide) {
// Provide "Parse" as a value in your module.
$provide.value('Parse', Parse);
});
inject(function ($controller, $injector) {
$scope = $injector.get('$rootScope').$new();
Parse = $injector.get('Parse'); // equal to: {} (at this point)
createController = function () {
return $controller('LoginCtrl', {
$scope: $scope,
Parse: Parse
});
};
});
Parse.user = {};
Parse.user.login = sinon.stub();
});
Parse 现在完全被删除了。它将是一个空对象,然后提供给模块,暴露在我们的规范中,注入我们的控制器,然后用与实际实现相匹配的存根方法进行装饰(当然,只有名称)。
预期
您应该在控制器规范中测试的不是 Parse
服务的内部行为,而是您的控制器使用正确的参数调用 Parse
的方法。
之后,您可以继续测试 您的控制器 和 其关联的 $scope 对 Parse.user.login()
响应。
是
注意:我正在使用 mocha/chai 语法编写这些规范 - 采用您认为合适的 jasmine 样式(老实说,不确定它如何与 sinon.js 一起使用)
it('calls the Parse method', function () {
createController();
$scope.login('username', 'pw', angular.noop);
expect(Parse.user.login).to.have
.been.calledOnce.and.calledWith('username', 'pw', angular.noop);
});
context('Parse response', function () {
it('failed', function () {
Parse.user.login.returns({ status: 'super_error' });
createController();
$scope.login('a', 'b', angular.noop);
expect(/* expect that something on $scope happened? */);
});
it('was succesful', function () {
Parse.user.login.returns({ status: 'great_success', data: {} });
createController();
$scope.login('a', 'b', angular.noop);
expect(/* expect that something on $scope happened? */);
});
});
我就是这么写的。但是 - 查看 Parse.user.logIn docs 我们可以看到方法 returns 是 promise
。
因此,您需要采取以下步骤来有效地消除正确的行为:
var $q;
// combine this beforeEach block with the first one we wrote.
beforeEach(function () {
inject(function ($injector) {
$q = $injector.get('$q');
});
});
context('parse.user.login', inject(function ($timeout) {
var success, error, opts;
beforeEach(function () {
success = sinon.stub();
error = sinon.stub();
opts = {
success: success,
error: error
};
});
it('failed', function () {
Parse.user.logIn.returns($q.reject({ status: 'bad_error' });
createController();
$scope.login('a', 'b', opts);
$timeout.flush(); // Either this, or $rootScope.$digest or .notify(done) to flush the promise.
expect(error).to.have.been.calledOnce;
});
it('was successful', function () {
Parse.user.logIn.returns($q.when({ status: 'yay!', data: {} });
createController();
$scope.login('a', 'b', opts);
$timeout.flush(); // Either this, or $rootScope.$digest or .notify(done) to flush the promise.
expect(success).to.have.been.calledOnce;
});
}));
我正在为控制器编写单元测试,该控制器具有 angular 作用域函数,调用时会调用 parse.com 框架函数,调用解析服务器和 returns 成功参数或错误代码。
如何为单元测试模拟该对象?
控制器范围内的函数名称是Parse.User.logIn,这是我到目前为止为它制作的模拟对象。
mockParse = {
User: {
logIn: function(_userName, _password, callbackObj) {
callbackObj.success({
attributes:{
username: "testuser",
email: "testuser@example.com"
}
$rootScope.user = _user;
$rootScope.isLoggedIn = true;
$state.go('tab.home');
};
callbackObj.error({
err:{
code: 101
}
if (err.code == 101){
$scope.error.message = "invalid login credentials";
} else {
$scope.error.message = "Unexpected error"
}
});
}
}
}
我做对了吗?我是否必须为不同的回调响应、错误和成功创建不同的对象?
当我把它放在测试中时,我在哪里注入它?我是否像这样将它放在控制器中?:
ctrl = $controller("LoginCtrl", {
$scope: $scope,
Parse: mockParse
});
这是实际函数:
Parse.User.logIn(($scope.user.username) , $scope.user.password, {
success: function(_user) {
console.log('Login Success');
console.log('user = ' + _user.attributes.username);
console.log('email = ' + _user.attributes.email);
$ionicLoading.hide();
$rootScope.user = _user;
$rootScope.isLoggedIn = true;
$state.go('tab.home');
},
error: function(user, err) {
$ionicLoading.hide();
// The login failed. Check error to see why.
if (err.code === 101) {
$scope.error.message = 'Invalid login credentials';
} else {
$scope.error.message = 'An unexpected error has ' +
'occurred, please try again.';
}
console.log('error message on Parse.User.logIn: ' + $scope.error.message);
$scope.$apply();
}
});
前言:我不太精通Parse.js(也不是茉莉花)
我建议您去获取 sinonjs - 超级有效并且(一段时间后)易于使用 mocking/stubbing/spying。
首先不要担心 Parse
对象的内部实现,我们只对单元测试设置中的依赖项 input
和 output
感兴趣。应该没有理由在那个精细的等级细节中剔除服务对象。
beforeEach
var $scope, Parse, createController;
beforeEach(function () {
Parse = {};
module('your_module', function ($provide) {
// Provide "Parse" as a value in your module.
$provide.value('Parse', Parse);
});
inject(function ($controller, $injector) {
$scope = $injector.get('$rootScope').$new();
Parse = $injector.get('Parse'); // equal to: {} (at this point)
createController = function () {
return $controller('LoginCtrl', {
$scope: $scope,
Parse: Parse
});
};
});
Parse.user = {};
Parse.user.login = sinon.stub();
});
Parse 现在完全被删除了。它将是一个空对象,然后提供给模块,暴露在我们的规范中,注入我们的控制器,然后用与实际实现相匹配的存根方法进行装饰(当然,只有名称)。
预期
您应该在控制器规范中测试的不是 Parse
服务的内部行为,而是您的控制器使用正确的参数调用 Parse
的方法。
之后,您可以继续测试 您的控制器 和 其关联的 $scope 对 Parse.user.login()
响应。
是
注意:我正在使用 mocha/chai 语法编写这些规范 - 采用您认为合适的 jasmine 样式(老实说,不确定它如何与 sinon.js 一起使用)
it('calls the Parse method', function () {
createController();
$scope.login('username', 'pw', angular.noop);
expect(Parse.user.login).to.have
.been.calledOnce.and.calledWith('username', 'pw', angular.noop);
});
context('Parse response', function () {
it('failed', function () {
Parse.user.login.returns({ status: 'super_error' });
createController();
$scope.login('a', 'b', angular.noop);
expect(/* expect that something on $scope happened? */);
});
it('was succesful', function () {
Parse.user.login.returns({ status: 'great_success', data: {} });
createController();
$scope.login('a', 'b', angular.noop);
expect(/* expect that something on $scope happened? */);
});
});
我就是这么写的。但是 - 查看 Parse.user.logIn docs 我们可以看到方法 returns 是 promise
。
因此,您需要采取以下步骤来有效地消除正确的行为:
var $q;
// combine this beforeEach block with the first one we wrote.
beforeEach(function () {
inject(function ($injector) {
$q = $injector.get('$q');
});
});
context('parse.user.login', inject(function ($timeout) {
var success, error, opts;
beforeEach(function () {
success = sinon.stub();
error = sinon.stub();
opts = {
success: success,
error: error
};
});
it('failed', function () {
Parse.user.logIn.returns($q.reject({ status: 'bad_error' });
createController();
$scope.login('a', 'b', opts);
$timeout.flush(); // Either this, or $rootScope.$digest or .notify(done) to flush the promise.
expect(error).to.have.been.calledOnce;
});
it('was successful', function () {
Parse.user.logIn.returns($q.when({ status: 'yay!', data: {} });
createController();
$scope.login('a', 'b', opts);
$timeout.flush(); // Either this, or $rootScope.$digest or .notify(done) to flush the promise.
expect(success).to.have.been.calledOnce;
});
}));