在Angular单元测试中应该如何处理运行块?
How should the run block be dealt with in Angular unit tests?
我的理解是,当你在Angular单元测试中加载模块时,run
块会被调用。
run
块,因为单元测试应该只测试一个单元。这是真的吗?如果是,是否有办法阻止run
块运行?我的研究使我认为答案是"不",并且run
块总是在模块加载时运行,但也许有一种方法可以覆盖这一点。如果没有,我如何测试run
块?
块运行:
function run(Auth, $cookies, $rootScope) {
$rootScope.user = {};
Auth.getCurrentUser();
}
Auth.getCurrentUser:
getCurrentUser: function() {
// user is logged in
if (Object.keys($rootScope.user).length > 0) {
return $q.when($rootScope.user);
}
// user is logged in, but page has been refreshed and $rootScope.user is lost
if ($cookies.get('userId')) {
return $http.get('/current-user')
.then(function(response) {
angular.copy(response.data, $rootScope.user);
return $rootScope.user;
})
;
}
// user isn't logged in
else {
return $q.when({});
}
}
auth.factory.spec.js
describe('Auth Factory', function() {
var Auth, $httpBackend, $rootScope, $cookies, $q;
var user = {
username: 'a',
password: 'password',
};
var response = {
_id: 1,
local: {
username: 'a',
role: 'user'
}
};
function isPromise(el) {
return !!el.$$state;
}
beforeEach(module('mean-starter', 'ngCookies', 'templates'));
beforeEach(inject(function(_Auth_, _$httpBackend_, _$rootScope_, _$cookies_, _$q_) {
Auth = _Auth_;
$httpBackend = _$httpBackend_;
$rootScope = _$rootScope_;
$cookies = _$cookies_;
$q = _$q_;
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('#signup', function() {
$rootScope.user = {};
$httpBackend.expectPOST('/users', user).respond(response);
spyOn(angular, 'copy').and.callThrough();
spyOn($cookies, 'put').and.callThrough();
var retVal = Auth.signup(user);
$httpBackend.flush();
expect(angular.copy).toHaveBeenCalledWith(response, $rootScope.user);
expect($cookies.put).toHaveBeenCalledWith('userId', 1);
expect(isPromise(retVal)).toBe(true);
});
it('#login', function() {
$rootScope.user = {};
$httpBackend.expectPOST('/login', user).respond(response);
spyOn(angular, 'copy').and.callThrough();
spyOn($cookies, 'put').and.callThrough();
var retVal = Auth.login(user);
$httpBackend.flush();
expect(angular.copy).toHaveBeenCalledWith(response, $rootScope.user);
expect($cookies.put).toHaveBeenCalledWith('userId', 1);
expect(isPromise(retVal)).toBe(true);
});
it('#logout', function() {
$httpBackend.expectGET('/logout').respond();
spyOn(angular, 'copy').and.callThrough();
spyOn($cookies, 'remove');
Auth.logout();
$httpBackend.flush();
expect(angular.copy).toHaveBeenCalledWith({}, $rootScope.user);
expect($cookies.remove).toHaveBeenCalledWith('userId');
});
describe('#getCurrentUser', function() {
it('User is logged in', function() {
$rootScope.user = response;
spyOn($q, 'when').and.callThrough();
var retVal = Auth.getCurrentUser();
expect($q.when).toHaveBeenCalledWith($rootScope.user);
expect(isPromise(retVal)).toBe(true);
});
it('User is logged in but page has been refreshed', function() {
$cookies.put('userId', 1);
$httpBackend.expectGET('/current-user').respond(response);
spyOn(angular, 'copy').and.callThrough();
var retVal = Auth.getCurrentUser();
$httpBackend.flush();
expect(angular.copy).toHaveBeenCalledWith(response, $rootScope.user);
expect(isPromise(retVal)).toBe(true);
});
it("User isn't logged in", function() {
$rootScope.user = {};
$cookies.remove('userId');
spyOn($q, 'when').and.callThrough();
var retVal = Auth.getCurrentUser();
expect($q.when).toHaveBeenCalledWith({});
expect(isPromise(retVal)).toBe(true);
});
});
});
尝试1:
beforeEach(module('mean-starter', 'ngCookies', 'templates'));
beforeEach(inject(function(_Auth_, _$httpBackend_, _$rootScope_, _$cookies_, _$q_) {
Auth = _Auth_;
$httpBackend = _$httpBackend_;
$rootScope = _$rootScope_;
$cookies = _$cookies_;
$q = _$q_;
}));
beforeEach(function() {
spyOn(Auth, 'getCurrentUser');
});
afterEach(function() {
expect(Auth.getCurrentUser).toHaveBeenCalled();
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
这行不通。run
块在模块加载时运行,因此Auth.getCurrentUser()
在spy设置之前被调用。
Expected spy getCurrentUser to have been called.
尝试2:
beforeEach(inject(function(_Auth_, _$httpBackend_, _$rootScope_, _$cookies_, _$q_) {
Auth = _Auth_;
$httpBackend = _$httpBackend_;
$rootScope = _$rootScope_;
$cookies = _$cookies_;
$q = _$q_;
}));
beforeEach(function() {
spyOn(Auth, 'getCurrentUser');
});
beforeEach(module('mean-starter', 'ngCookies', 'templates'));
afterEach(function() {
expect(Auth.getCurrentUser).toHaveBeenCalled();
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
这不起作用,因为Auth
不能在我的应用模块加载之前被注入。
Error: [$injector:unpr] Unknown provider: AuthProvider <- Auth
尝试3:
正如你所看到的,这里有一个先有鸡还是先有蛋的问题。我需要在模块加载之前注入Auth并设置spy,但是我不能,因为Auth无法在模块加载之前注入。
这篇博文提到了先有鸡还是先有蛋的问题,并提供了一个有趣的潜在解决方案。作者建议我应该在加载模块之前使用$provide
手动创建我的Auth
服务。因为我是在创建服务,而不是注入它,所以我可以在模块被加载之前创建服务,这样我就可以设置spy了。然后,当模块被加载时,它将使用这个创建的模拟服务。
describe('example', function () {
var loggingService;
beforeEach(function () {
module('example', function ($provide) {
$provide.value('loggingService', {
start: jasmine.createSpy()
});
});
inject(function (_loggingService_) {
loggingService = _loggingService_;
});
});
it('should start logging service', function() {
expect(loggingService.start).toHaveBeenCalled();
});
});
问题是,我需要我的Auth
服务!我只想在run
块中使用mock;我需要我真正的Auth
服务在其他地方,所以我可以测试它。
我想我可以使用$provide
创建实际的Auth
服务,但感觉不对。
最后一个问题-无论我最终使用什么代码来处理这个run
块问题,是否有一种方法可以让我将其提取出来,这样我就不必为我的每个规范文件重新编写它?我能想到的唯一方法就是使用某种全局函数。
auth.factory.js
angular
.module('mean-starter')
.factory('Auth', Auth)
;
function Auth($http, $state, $window, $cookies, $q, $rootScope) {
return {
signup: function(user) {
return $http
.post('/users', user)
.then(function(response) {
angular.copy(response.data, $rootScope.user);
$cookies.put('userId', response.data._id);
$state.go('home');
})
;
},
login: function(user) {
return $http
.post('/login', user)
.then(function(response) {
angular.copy(response.data, $rootScope.user);
$cookies.put('userId', response.data._id);
$state.go('home');
})
;
},
logout: function() {
$http
.get('/logout')
.then(function() {
angular.copy({}, $rootScope.user);
$cookies.remove('userId');
$state.go('home');
})
.catch(function() {
console.log('Problem logging out.');
})
;
},
getCurrentUser: function() {
// user is logged in
if (Object.keys($rootScope.user).length > 0) {
return $q.when($rootScope.user);
}
// user is logged in, but page has been refreshed and $rootScope.user is lost
if ($cookies.get('userId')) {
return $http.get('/current-user')
.then(function(response) {
angular.copy(response.data, $rootScope.user);
return $rootScope.user;
})
;
}
// user isn't logged in
else {
return $q.when({});
}
}
};
}
编辑-尝试失败+尝试成功:
beforeEach(module('auth'));
beforeEach(inject(function(_Auth_) {
Auth = _Auth_;
spyOn(Auth, 'requestCurrentUser');
}));
beforeEach(module('mean-starter', 'ngCookies', 'templates'));
beforeEach(inject(function(_Auth_, _$httpBackend_, _$rootScope_, _$cookies_, _$q_) {
// Auth = _Auth_;
$httpBackend = _$httpBackend_;
$rootScope = _$rootScope_;
$cookies = _$cookies_;
$q = _$q_;
}));
// beforeEach(function() {
// spyOn(Auth, 'getCurrentUser');
// });
afterEach(function() {
expect(Auth.getCurrentUser).toHaveBeenCalled();
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
我不知道为什么这不会工作(独立于使用inject
两次的问题)。
我试图绕过必须使用$provide
,因为最初对我来说感觉很hacky/奇怪。经过更多的思考,我现在觉得$provide
很好,按照你的建议使用mock-auth
是非常棒的!!都对我有用。
在auth.factory.spec.js
中,我刚刚加载了auth
模块(我称之为auth
,而不是mean-auth
),而没有加载mean-starter
。这没有run
块问题,因为该模块没有run
块代码,但它允许我测试我的Auth
工厂。在其他地方,这是有效的:
beforeEach(module('mean-starter', 'templates', function($provide) {
$provide.value('Auth', {
requestCurrentUser: jasmine.createSpy()
});
}));
神奇的mock-auth
解决方案也是如此:
auth.factory.mock.js
angular
.module('mock-auth', [])
.factory('Auth', Auth)
;
function Auth() {
return {
requestCurrentUser: jasmine.createSpy()
};
}
user.service.spec.js
beforeEach(module('mean-starter', 'mock-auth', 'templates'));
我的理解是,当你在Angular单元中加载模块时测试时,调用运行块。
正确的。
我认为如果你正在测试一个组件,你不会想要这样做同时测试运行块,因为单元测试是应该只测试一个单元。这是真的吗?
同样正确,因为现在您正在有效地测试Auth
和运行块的集成,并且两者之间没有隔离。
如果是,是否有办法防止运行块运行?我的研究让我认为答案是"不",而且我认为块总是在模块加载时运行,但也许有一种方法来推翻这个。如果不是,我该如何测试运行块?
作为实现,没有你不能阻止运行块运行。但是,由于您的问题最终是一个模块化问题,因此通过一些小的重构仍然是可能的。在看不到模块声明的情况下,我想象它看起来像这样:
angular.module('mean-starter', ['ngCookies'])
.factory('Auth', function($cookies) {
...
});
.run(function(Auth, $rootScope) {
...
});
这个模式可以被分解成模块以支持可测试性(和模块可重用性):
angular.module('mean-auth', ['ngCookies'])
.factory('Auth', function() {
...
});
angular.module('mean-starter', ['mean-auth'])
.run(function(Auth, $rootScope) {
...
});
现在允许您通过仅将mean-auth
模块加载到其测试中来隔离测试Auth
工厂。
虽然这解决了运行块干扰Auth
单元测试的问题,但您仍然面临模拟Auth.getCurrentUser
以便单独测试运行块的问题。您引用的博客文章是正确的,因为您应该寻求利用模块的配置阶段来存根/监视在运行阶段使用的依赖项。因此,在您的测试中:
module('mean-starter', function ($provide) {
$provide.value('Auth', {
getCurrentUser: jasmine.createSpy()
});
});
关于您的最后一个问题,您可以通过将它们声明为模块来创建可重用的mock。例如,如果您想为Auth
创建一个可重用的模拟工厂,您可以在单元测试之前加载的单独文件中定义它:
angular.module('mock-auth', [])
.factory('Auth', function() {
return {
getCurrentUser: jasmine.createSpy()
};
});
,然后在你需要它的模块后面的测试中加载它,因为angular会覆盖任何同名的服务:
module('mean-starter', 'mock-auth');
- 错误“;未处理'错误'事件“;在运行yo发电机之后
- 在Node Webkit应用程序中从DOM单击按钮时运行批处理文件
- 如何在node+express中重新运行请求处理程序
- 即使被信号处理程序中断,node.js代码也会一直运行到完成吗
- 通过从文本区域获取代码,在画布中运行处理代码
- stopPropagation是否还会阻止同一元素上的其他处理程序运行
- 运行 AJAX 需要什么 - 将 jQuery 添加到页面处理 AJAX 调用
- jQuery成功处理程序未在生产服务器上执行,但在dev中运行良好
- jQuery的事件处理程序运行良好,但应该't,因为它's不在ready()函数中
- 如何使用webpack处理仅在运行时可用的服务器端依赖关系
- 为什么我处理的网络游戏只能在Chrome中运行
- 是JavaScript(jQuery)和一个处理程序,每次从内存运行或读取
- "npm运行构建“;模块解析错误”;您可能需要一个适当的加载程序来处理此文件类型&”;
- 使用运行时创建的链接来触发ASP.Net中隐藏按钮的单击处理程序
- 如何使[Enter]只运行一个处理程序
- Electron和Typescript:如何在运行时正确处理模块
- 如何从 DOS 批处理脚本或计划运行 imacros .js
- 谷歌脚本 :如何在另一个函数正在运行时从服务器处理程序关闭 UI
- 使用批处理文件运行咕噜声构建
- 在Angular单元测试中应该如何处理运行块?