Karma/Jasmine 测试离子应用程序不注入控制器
Karma/Jasmine test of ionic application not injecting controller
我正在使用 Karma 和 Jasmine 向离子应用程序添加一些测试。此时此刻,我正在尝试对 angular 登录控制器进行测试。但是,它似乎没有执行创建控制器并模拟所有外部依赖项的注入语句。这是我正在使用的代码:
login.controller.js
(function() {
var app = angular.module("myApp");
app.controller("LoginController", function($translate, $state, $ionicPopup, $localStorage, sessionService, userService) {
var vm = this;
vm.email = null;
vm.password = null;
vm.login = function() {
$localStorage.email = vm.email;
$localStorage.password = vm.password;
sessionService.login().then(
function(data) {
if (userService.isAuthenticated()) {
// User is logged, go to the next state
$state.go("next_state");
} else {
// User is not logged (invalid credentials), show an alert message
var alertPopup = $ionicPopup.alert({
title: $translate.instant("LOGIN_FAIL_TITLE"),
template: $translate.instant("LOGIN_FAIL_MESSAGE")
});
}
}
);
}
});
})();
login.controller.tests.js
describe("LoginController", function() {
var controller,
deferredLogin,
translateMock,
stateMock,
ionicPopupMock,
localStorageMock,
sessionServiceMock,
userServiceMock;
beforeEach(module("myApp"));
beforeEach(module(function($provide, $urlRouterProvider) {
$provide.value("$ionicTemplateCache", function() {});
$urlRouterProvider.deferIntercept();
}));
beforeEach(inject(function($controller, $q) {
deferredLogin = $q.defer();
sessionServiceMock = {
login: jasmine.createSpy("login spy")
.and.returnValue(deferredLogin.promise)
};
translateMock = jasmine.createSpyObj("$translate spy", ["instant"]);
stateMock = jasmine.createSpyObj("$state spy", ["go"]);
ionicPopupMock = jasmine.createSpyObj("$ionicPopup spy", ["alert"]);
localStorageMock = jasmine.createSpyObj("$localStorage spy", ["getItem"]);
userServiceMock = jasmine.createSpyObj("userService spy", ["isAuthenticated"]);
controller = $controller("LoginController", {
"$translate": translateMock,
"$state": stateMock,
"$ionicPopup": ionicPopupMock,
"$localStorage": localStorageMock,
"sessionService": sessionServiceMock,
"userService": userServiceMock
});
}));
describe("#login", function() {
beforeEach(inject(function(_$rootScope_) {
$rootScope = _$rootScope_;
controller.email = "user@email.com";
controller.password = "foobarfoo";
controller.login();
}));
it("should call login on sessionService", function() {
expect(sessionServiceMock.login).toHaveBeenCalledWith("user@email.com", "foobarfoo");
});
describe("when the login is executed", function() {
it("if successful, should change state to next_state", function() {
deferredLogin.resolve();
$rootScope.$digest();
expect(stateMock.go).toHaveBeenCalledWith("next_state");
});
it("if unsuccessful, should show a popup", function() {
deferredLogin.reject();
$rootScope.$digest();
expect(ionicPopupMock.alert).toHaveBeenCalled();
});
});
});
});
我一直在搜索所有地方尝试找到不同的解决方案,但我总是得到相同的结果:
...
PhantomJS 2.1.1 (Linux 0.0.0) LoginController #login should call login on sessionService FAILED
forEach@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:13691:24
loadModules@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17878:12
createInjector@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17800:30
workFn@/home/user/NSS/my-app/www/lib/angular-mocks/angular-mocks.js:2922:60
loaded@http://localhost:9876/context.js:151:17
/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17918:53
forEach@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:13691:24
loadModules@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17878:12
createInjector@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17800:30
workFn@/home/user/NSS/my-app/www/lib/angular-mocks/angular-mocks.js:2922:60
loaded@http://localhost:9876/context.js:151:17
/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17918:53
TypeError: undefined is not an object (evaluating 'sessionServiceMock.login') in unit-tests/controllers/login.controller.tests.js (line 63)
unit-tests/controllers/login.controller.tests.js:63:32
loaded@http://localhost:9876/context.js:151:17
PhantomJS 2.1.1 (Linux 0.0.0) LoginController #login when the login is executed if successful, should change state to capture_image FAILED
forEach@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:13691:24
loadModules@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17878:12
createInjector@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17800:30
workFn@/home/user/NSS/my-app/www/lib/angular-mocks/angular-mocks.js:2922:60
loaded@http://localhost:9876/context.js:151:17
/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17918:53
forEach@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:13691:24
loadModules@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17878:12
createInjector@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17800:30
workFn@/home/user/NSS/my-app/www/lib/angular-mocks/angular-mocks.js:2922:60
loaded@http://localhost:9876/context.js:151:17
/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17918:53
TypeError: undefined is not an object (evaluating 'deferredLogin.resolve') in unit-tests/controllers/login.controller.tests.js (line 68)
unit-tests/controllers/login.controller.tests.js:68:22
loaded@http://localhost:9876/context.js:151:17
PhantomJS 2.1.1 (Linux 0.0.0) LoginController #login when the login is executed if unsuccessful, should show a popup FAILED
forEach@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:13691:24
loadModules@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17878:12
createInjector@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17800:30
workFn@/home/user/NSS/my-app/www/lib/angular-mocks/angular-mocks.js:2922:60
loaded@http://localhost:9876/context.js:151:17
/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17918:53
forEach@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:13691:24
loadModules@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17878:12
createInjector@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17800:30
workFn@/home/user/NSS/my-app/www/lib/angular-mocks/angular-mocks.js:2922:60
loaded@http://localhost:9876/context.js:151:17
/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17918:53
TypeError: undefined is not an object (evaluating 'deferredLogin.reject') in unit-tests/controllers/login.controller.tests.js (line 74)
unit-tests/controllers/login.controller.tests.js:74:22
loaded@http://localhost:9876/context.js:151:17
...
我不明白我做错了什么。提前致谢。
更新:
正如 Matthew Green 所说,我已经改变了编写测试的方式。现在他们遵循他的建议,我还添加了一些必要的注射剂:
describe("LoginController", function() {
var controller,
stateMock,
ionicPopupMock,
localStorageMock,
sessionServiceMockPromise,
sessionServiceMock;
// Load the application module
beforeEach(module("myApp"));
// Avoid trying to load all templates of the application
beforeEach(module(function($provide, $urlRouterProvider) {
$provide.value("$ionicTemplateCache", function() {});
$urlRouterProvider.deferIntercept();
}));
// Avoid asynchronous loader of the translations (problem with the angular-translate-design)
beforeEach(module(function($provide, $translateProvider) {
$provide.factory("customLoader", function($q) {
return function() {
var deferred = $q.defer();
deferred.resolve({});
return deferred.promise;
};
});
$translateProvider.useLoader("customLoader");
}));
// Instanciate and initialize the controller and mocks
beforeEach(inject(function($controller, $q) {
sessionServiceMockPromise = $q.defer();
sessionServiceMock = jasmine.createSpyObj("sessionServiceMock", ["login"]);
sessionServiceMock.login.and.callFake(function() {
return sessionServiceMockPromise.promise;
});
stateMock = jasmine.createSpyObj("$state spy", ["go"]);
ionicPopupMock = jasmine.createSpyObj("$ionicPopup spy", ["alert"]);
localStorageMock = jasmine.createSpyObj("$localStorage spy", ["getItem"]);
controller = $controller("LoginController", {
"$state": stateMock,
"$ionicPopup": ionicPopupMock,
"$localStorage": localStorageMock,
"sessionService": sessionServiceMock
});
}));
describe("#login", function() {
beforeEach(inject(function(_$rootScope_) {
$rootScope = _$rootScope_;
controller.login();
sessionServiceMockPromise.resolve();
}));
it("should call login on sessionService", function() {
expect(sessionServiceMock.login).toHaveBeenCalledWith();
});
describe("when the login is executed", function() {
it("if successfull, should change state to capture_image", inject(function(userService) {
spyOn(userService, "isAuthenticated").and.callFake(function() { return true; });
$rootScope.$digest();
expect(stateMock.go).toHaveBeenCalledWith("nextState");
}));
it("should show a popup", inject(function(userService) {
spyOn(userService, "isAuthenticated").and.callFake(function() { return false; });
$rootScope.$digest();
expect(ionicPopupMock.alert).toHaveBeenCalled();
}));
});
});
});
看起来你的 sessionService.login()
是一个应该 return 承诺的函数。既然如此,我发现您所拥有的存在一些问题。
首先,我不知道您为什么选择设置与其他服务不同的这项服务。这很可能是您的一些问题。如果您将其设置为与其他设置相同,它将看起来像这样。
var sessionServiceMockPromise = $q.defer();
sessionServiceMock = jasmine.createSpyObj('sessionServiceMock', ['login']);
sessionServiceMock.login.and.callFake(function() {
return sessionServiceMockPromise.promise;
});
在此我们确定登录是 return 承诺的功能。使用 sessionServiceMockPromise
,您现在可以在测试中调用 sessionServiceMockPromise.resolve([data])
(或 reject()
)以查看您的承诺的结果。
其次,您的测试表明它希望使用两个变量调用登录。在您的服务或测试代码中,我没有看到这是接受变量的函数。更好的测试可能只是调用它或将状态设置为您所期望的。
我正在使用 Karma 和 Jasmine 向离子应用程序添加一些测试。此时此刻,我正在尝试对 angular 登录控制器进行测试。但是,它似乎没有执行创建控制器并模拟所有外部依赖项的注入语句。这是我正在使用的代码:
login.controller.js
(function() {
var app = angular.module("myApp");
app.controller("LoginController", function($translate, $state, $ionicPopup, $localStorage, sessionService, userService) {
var vm = this;
vm.email = null;
vm.password = null;
vm.login = function() {
$localStorage.email = vm.email;
$localStorage.password = vm.password;
sessionService.login().then(
function(data) {
if (userService.isAuthenticated()) {
// User is logged, go to the next state
$state.go("next_state");
} else {
// User is not logged (invalid credentials), show an alert message
var alertPopup = $ionicPopup.alert({
title: $translate.instant("LOGIN_FAIL_TITLE"),
template: $translate.instant("LOGIN_FAIL_MESSAGE")
});
}
}
);
}
});
})();
login.controller.tests.js
describe("LoginController", function() {
var controller,
deferredLogin,
translateMock,
stateMock,
ionicPopupMock,
localStorageMock,
sessionServiceMock,
userServiceMock;
beforeEach(module("myApp"));
beforeEach(module(function($provide, $urlRouterProvider) {
$provide.value("$ionicTemplateCache", function() {});
$urlRouterProvider.deferIntercept();
}));
beforeEach(inject(function($controller, $q) {
deferredLogin = $q.defer();
sessionServiceMock = {
login: jasmine.createSpy("login spy")
.and.returnValue(deferredLogin.promise)
};
translateMock = jasmine.createSpyObj("$translate spy", ["instant"]);
stateMock = jasmine.createSpyObj("$state spy", ["go"]);
ionicPopupMock = jasmine.createSpyObj("$ionicPopup spy", ["alert"]);
localStorageMock = jasmine.createSpyObj("$localStorage spy", ["getItem"]);
userServiceMock = jasmine.createSpyObj("userService spy", ["isAuthenticated"]);
controller = $controller("LoginController", {
"$translate": translateMock,
"$state": stateMock,
"$ionicPopup": ionicPopupMock,
"$localStorage": localStorageMock,
"sessionService": sessionServiceMock,
"userService": userServiceMock
});
}));
describe("#login", function() {
beforeEach(inject(function(_$rootScope_) {
$rootScope = _$rootScope_;
controller.email = "user@email.com";
controller.password = "foobarfoo";
controller.login();
}));
it("should call login on sessionService", function() {
expect(sessionServiceMock.login).toHaveBeenCalledWith("user@email.com", "foobarfoo");
});
describe("when the login is executed", function() {
it("if successful, should change state to next_state", function() {
deferredLogin.resolve();
$rootScope.$digest();
expect(stateMock.go).toHaveBeenCalledWith("next_state");
});
it("if unsuccessful, should show a popup", function() {
deferredLogin.reject();
$rootScope.$digest();
expect(ionicPopupMock.alert).toHaveBeenCalled();
});
});
});
});
我一直在搜索所有地方尝试找到不同的解决方案,但我总是得到相同的结果:
...
PhantomJS 2.1.1 (Linux 0.0.0) LoginController #login should call login on sessionService FAILED
forEach@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:13691:24
loadModules@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17878:12
createInjector@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17800:30
workFn@/home/user/NSS/my-app/www/lib/angular-mocks/angular-mocks.js:2922:60
loaded@http://localhost:9876/context.js:151:17
/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17918:53
forEach@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:13691:24
loadModules@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17878:12
createInjector@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17800:30
workFn@/home/user/NSS/my-app/www/lib/angular-mocks/angular-mocks.js:2922:60
loaded@http://localhost:9876/context.js:151:17
/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17918:53
TypeError: undefined is not an object (evaluating 'sessionServiceMock.login') in unit-tests/controllers/login.controller.tests.js (line 63)
unit-tests/controllers/login.controller.tests.js:63:32
loaded@http://localhost:9876/context.js:151:17
PhantomJS 2.1.1 (Linux 0.0.0) LoginController #login when the login is executed if successful, should change state to capture_image FAILED
forEach@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:13691:24
loadModules@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17878:12
createInjector@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17800:30
workFn@/home/user/NSS/my-app/www/lib/angular-mocks/angular-mocks.js:2922:60
loaded@http://localhost:9876/context.js:151:17
/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17918:53
forEach@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:13691:24
loadModules@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17878:12
createInjector@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17800:30
workFn@/home/user/NSS/my-app/www/lib/angular-mocks/angular-mocks.js:2922:60
loaded@http://localhost:9876/context.js:151:17
/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17918:53
TypeError: undefined is not an object (evaluating 'deferredLogin.resolve') in unit-tests/controllers/login.controller.tests.js (line 68)
unit-tests/controllers/login.controller.tests.js:68:22
loaded@http://localhost:9876/context.js:151:17
PhantomJS 2.1.1 (Linux 0.0.0) LoginController #login when the login is executed if unsuccessful, should show a popup FAILED
forEach@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:13691:24
loadModules@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17878:12
createInjector@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17800:30
workFn@/home/user/NSS/my-app/www/lib/angular-mocks/angular-mocks.js:2922:60
loaded@http://localhost:9876/context.js:151:17
/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17918:53
forEach@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:13691:24
loadModules@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17878:12
createInjector@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17800:30
workFn@/home/user/NSS/my-app/www/lib/angular-mocks/angular-mocks.js:2922:60
loaded@http://localhost:9876/context.js:151:17
/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17918:53
TypeError: undefined is not an object (evaluating 'deferredLogin.reject') in unit-tests/controllers/login.controller.tests.js (line 74)
unit-tests/controllers/login.controller.tests.js:74:22
loaded@http://localhost:9876/context.js:151:17
...
我不明白我做错了什么。提前致谢。
更新:
正如 Matthew Green 所说,我已经改变了编写测试的方式。现在他们遵循他的建议,我还添加了一些必要的注射剂:
describe("LoginController", function() {
var controller,
stateMock,
ionicPopupMock,
localStorageMock,
sessionServiceMockPromise,
sessionServiceMock;
// Load the application module
beforeEach(module("myApp"));
// Avoid trying to load all templates of the application
beforeEach(module(function($provide, $urlRouterProvider) {
$provide.value("$ionicTemplateCache", function() {});
$urlRouterProvider.deferIntercept();
}));
// Avoid asynchronous loader of the translations (problem with the angular-translate-design)
beforeEach(module(function($provide, $translateProvider) {
$provide.factory("customLoader", function($q) {
return function() {
var deferred = $q.defer();
deferred.resolve({});
return deferred.promise;
};
});
$translateProvider.useLoader("customLoader");
}));
// Instanciate and initialize the controller and mocks
beforeEach(inject(function($controller, $q) {
sessionServiceMockPromise = $q.defer();
sessionServiceMock = jasmine.createSpyObj("sessionServiceMock", ["login"]);
sessionServiceMock.login.and.callFake(function() {
return sessionServiceMockPromise.promise;
});
stateMock = jasmine.createSpyObj("$state spy", ["go"]);
ionicPopupMock = jasmine.createSpyObj("$ionicPopup spy", ["alert"]);
localStorageMock = jasmine.createSpyObj("$localStorage spy", ["getItem"]);
controller = $controller("LoginController", {
"$state": stateMock,
"$ionicPopup": ionicPopupMock,
"$localStorage": localStorageMock,
"sessionService": sessionServiceMock
});
}));
describe("#login", function() {
beforeEach(inject(function(_$rootScope_) {
$rootScope = _$rootScope_;
controller.login();
sessionServiceMockPromise.resolve();
}));
it("should call login on sessionService", function() {
expect(sessionServiceMock.login).toHaveBeenCalledWith();
});
describe("when the login is executed", function() {
it("if successfull, should change state to capture_image", inject(function(userService) {
spyOn(userService, "isAuthenticated").and.callFake(function() { return true; });
$rootScope.$digest();
expect(stateMock.go).toHaveBeenCalledWith("nextState");
}));
it("should show a popup", inject(function(userService) {
spyOn(userService, "isAuthenticated").and.callFake(function() { return false; });
$rootScope.$digest();
expect(ionicPopupMock.alert).toHaveBeenCalled();
}));
});
});
});
看起来你的 sessionService.login()
是一个应该 return 承诺的函数。既然如此,我发现您所拥有的存在一些问题。
首先,我不知道您为什么选择设置与其他服务不同的这项服务。这很可能是您的一些问题。如果您将其设置为与其他设置相同,它将看起来像这样。
var sessionServiceMockPromise = $q.defer();
sessionServiceMock = jasmine.createSpyObj('sessionServiceMock', ['login']);
sessionServiceMock.login.and.callFake(function() {
return sessionServiceMockPromise.promise;
});
在此我们确定登录是 return 承诺的功能。使用 sessionServiceMockPromise
,您现在可以在测试中调用 sessionServiceMockPromise.resolve([data])
(或 reject()
)以查看您的承诺的结果。
其次,您的测试表明它希望使用两个变量调用登录。在您的服务或测试代码中,我没有看到这是接受变量的函数。更好的测试可能只是调用它或将状态设置为您所期望的。