【发布时间】:2015-11-17 02:32:12
【问题描述】:
我的理解是,当您在 Angular 单元测试中加载模块时,run 块会被调用。
我认为,如果您正在测试一个组件,您不会希望同时测试 run 块,因为 unit 测试应该只测试一个 单位。这是真的吗?
如果是这样,有没有办法阻止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()。
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。
This 博客文章提到了鸡蛋问题并提供了一个有趣的潜在解决方案。作者建议我应该在加载模块之前使用$provide 手动创建Auth 服务。因为我是在创建服务,而不是注入它,所以我可以在加载模块之前完成它,并且我可以设置间谍。然后在加载模块时,它会使用这个创建的模拟服务。
这是他的示例代码:
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 块使用模拟;我在别处需要我真正的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,因为最初我觉得这很奇怪/奇怪。不过再想一想,我现在觉得$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'));
【问题讨论】:
-
与您的问题无关,但您应该知道
.run不会等待$http完成。如果应用程序中的任何内容都依赖于结果,那么您就有了竞争条件。通常,如果您使用的是ngRoute或ui.router,您将使用resolve。
标签: javascript angularjs unit-testing karma-runner