Karma Unexpected Request: GET modules/home/home.html - angularjs

I just switched my application from using the Jade template engine to use client side HTML in order to improve performance and decrease server requests. Everything is working fine in the application however I'm having an issue updating my unit tests.
I have the following test:
describe('Registration Controller Tests', function() {
var $controller, $scope, defer, registerSpy, doesUserExistSpy, auth, RegistrationCtrl;
beforeEach(module('enigmaApp'));
beforeEach(inject(function (_$controller_, _$rootScope_, $q) {
$controller = _$controller_;
$scope = _$rootScope_;
defer = $q.defer();
// Create spies
registerSpy = jasmine.createSpy('register').and.returnValue(defer.promise);
doesUserExistSpy = jasmine.createSpy('doesUserExist').and.returnValue(defer.promise);
auth = {
register: registerSpy,
doesUserExist: doesUserExistSpy
};
// Init register controller with mocked services and scope
RegistrationCtrl = $controller('RegistrationCtrl', {
$scope: $scope,
auth: auth
});
// digest to update controller with services and scope
$scope.$digest();
}));
describe('RegistrationCtrl.register()', function () {
beforeEach(function () {
$scope.user = {
email: 'bwayne#wayneenterprise.com',
first_name: 'Bruce',
last_name: 'Wyane',
password: 'password123'
}
});
it('should call auth.register() with $scope.user', function () {
$scope.register();
expect(auth.register).toHaveBeenCalledWith($scope.user);
});
});
Which results in the following error:
Error: Unexpected request: GET modules/home/home.html
No more requests expected
Any ideas what I need to do in order to mock the routes? I've tried a few things but nothings working so far.
Additional code:
RegistrationCtrl
.controller('RegistrationCtrl', function($scope, $state, auth) {
$scope.user = {};
$scope.userExists = false;
$scope.error = '';
$scope.register = function() {
auth.register($scope.user)
.then(function(response){
$state.go('secure.user');
})
.catch(function(err){
$scope.error = err;
});
};
});

assuming your static files are all in /modules:
$httpBackend.whenGET(/modules\/[\w\W]*/).passThrough();

Related

angularjs unit testing (cannot find propery of 'resolve' undefined angularjs testing)

I am trying to do unit test of my angular app with karma. I am getting some error. Am i missing something? A
This my controller
(function () {
"use strict"
angular
.module("myApp")
.controller("userCtrl",['$scope', '$state', 'userService', 'appSettings','md5','currentUser','$rootScope',
function ($scope, $state, userService, appSettings,md5,currentUser, $rootScope) {
$scope.login = function() {
$scope.loading = true;
if($scope.password != null){
var user ={
username:$scope.username,
password:md5.createHash($scope.password)
}
var getData = userService.login(user);
getData.then(function (response) {
console.log(response);
$scope.loading = false;
currentUser.setProfile(user.username, response.data.sessionId);
$state.go('videos');
}, function (response) {
console.log(response.data);
});
}else{
$scope.msg = "Password field is empty!"
}
}
}])
}());
This is my test codes
'use strict';
describe('userCtrl', function() {
beforeEach(module('myApp'));
var scope, userCtrl, apiService,q, deferred, currentUser;
describe('$scope.login', function(){
beforeEach(function(){
apiService = {
login: function () {
deferred = q.defer();
return deferred.promise;
};
};
});
beforeEach(inject(function($controller, $rootScope, $q, _currentUser_){
var user ={name:'ali',password:'password'};
scope = $rootScope.$new();
q = $q;
// The injector unwraps the underscores (_) from around the parameter names when matching
userCtrl = $controller('userCtrl', {
$scope:scope,
userService:apiService
});
//userService = _userService_;
currentUser = _currentUser_;
}));
it('should call user service login', function() {
spyOn(apiService, 'login').and.callThrough();
scope.login();
deferred.resolve(user);
expect(apiService.login).toHaveBeenCalled();
});
it('checks the password field', function() {
scope.login();
expect(scope.msg).toEqual('Password field is empty!');
});
});
});
And i am getting this error
enter image description here
If you have to test controller then use to spyon for service method and in case of service then use HttpBackend
describe('Testing a Controller that uses a Promise', function() {
var $scope;
var $q;
var deferred;
beforeEach(module('search'));
beforeEach(inject(function($controller, _$rootScope_, _$q_, searchService) {
$q = _$q_;
$scope = _$rootScope_.$new();
// We use the $q service to create a mock instance of defer
deferred = _$q_.defer();
// Use a Jasmine Spy to return the deferred promise
spyOn(searchService, 'search').and.returnValue(deferred.promise);
// Init the controller, passing our spy service instance
$controller('SearchController', {
$scope: $scope,
searchService: searchService
});
}));
it('should resolve promise', function() {
// Setup the data we wish to return for the .then function in the controller
deferred.resolve([{
id: 1
}, {
id: 2
}]);
// We have to call apply for this to work
$scope.$apply();
// Since we called apply, not we can perform our assertions
expect($scope.results).not.toBe(undefined);
expect($scope.error).toBe(undefined);
});
});
This for same using spyon for service method then use $appy method to make it work.

Jasmine Testing Controller and mock returned data in Service Promise

i have a problem when mocking a data service promise.
I use AngularJS and Jasmine 2.2.0
My Code is:
Controller
app.controller('homeController', ['$scope', 'ngAppSettings', 'homeViewModel', 'storageService', 'homeService',
function ($scope, ngAppSettings, homeViewModel, storageService, homeService) {
$scope.applicationName = ngAppSettings.applicationName;
$scope.model = homeViewModel;
storageService.getStorageAuth().then(function (data) {
$scope.model.userName= data.name;
}, function (err) {
alert(err.errors[0].message);
});
}]);
Service
app.service('storageService', ['$q', 'ngAppSettings', 'localStorageService',
function ($q, ngAppSettings, localStorageService) {
this.getStorageAuth = function () {
var deferred = $q.defer();
var userAuth = localStorageService.get(ngAppSettings.storageUser);
if (userAuth) {
data = userAuth;
}
deferred.resolve(data);
return deferred.promise;
};
}]);
Test Spec
describe('Controllers: homeController', function () {
var $rootScope;
var $scope;
var ctrl;
var $q;
var deferred;
var storageService;
beforeEach(module('IspFrontEndTemplateApp'));
beforeEach(inject(function (_$q_, _storageService_) {
$q = _$q_;
storageService = _storageService_;
deferred = $q.defer();
spyOn(storageService, "getStorageAuth").and.returnValue(deferred.promise);
}));
beforeEach(inject(function (_$rootScope_, $controller) {
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
ctrl = $controller('homeController', { $scope: $scope });
}));
it('UserName is: UsuarioTeste', function () {
deferred.resolve({
isAuth: true,
userName: "UsuarioTeste",
name: "UsuarioTeste"
});
expect($scope.model.userName).toBe('UsuarioTeste');
});
});
The error is: Expected '' to be 'UsuarioTeste'.
I need test my model properties in controller but the value is not refreshed
You need to $digest one more time in your test:
$scope.$digest();
expect($scope.model.userName).toBe('UsuarioTeste'); // will work
The reason is that promises are resolved asynchronously in the sense that a promise never calls its success or error callbacks within the same $apply it has been created in, even if it has been resolved. Code could be hard to read if you create a promise in the beginning of your function and the effect of the resolved promise would be already observable a few lines later.

Issue with closing login module for jasmine testing

I have an application which uses angulars $modal to popup a login modal any time a user tried to enter a secure route without a valid authentication token. This works great but is causing an issue with my testing.
The modal was created as a factory
.factory('loginModal', function ($modal) {
return function() {
var instance = $modal.open({
templateUrl: 'partials/login',
controller: 'AuthCtrl',
controllerAs: 'AuthCtrl'
})
return instance.result;
};
});
In my controller I have a login action, upon successfully logging in the modal is closed using $scope.$close.
$scope.login = function() {
auth.login($scope.user)
.then(function(response) {
$scope.$close(response);
$state.go('secure.user');
}, function(response) {
$scope.hasErrMsg = true;
$scope.errMsg = 'Incorrect password.';
$scope.$dismiss;
});
};
Lastly my unit test which is checking to make sure that auth.login is called with the correct properties when my controllers login function is called.
describe('Auth Controller Tests', function () {
var $scope, $controller, $q, $httpBackend, auth, controller, deferred, loginReqHandler, userReqHandler, indexReqHandler, registerPostReqHandler, doesUserExistPostReqHandler, loginPostReqHandler, loginModal;
beforeEach(module('enigmaApp'));
beforeEach(inject(function ($injector) {
$scope = $injector.get('$rootScope');
$controller = $injector.get('$controller');
$q = $injector.get('$q');
$httpBackend = $injector.get('$httpBackend');
auth = $injector.get('auth');
controller = $controller('AuthCtrl', { $scope: $scope });
deferred = $q.defer();
spyOn(auth, 'isLoggedIn');
loginReqHandler = $httpBackend.when('GET', 'partials/login').respond(deferred.promise);
userReqHandler = $httpBackend.when('GET', 'partials/user').respond(deferred.promise);
indexReqHandler = $httpBackend.when('GET', 'partials/index').respond(deferred.promise);
registerPostReqHandler = $httpBackend.when('POST', '/register').respond(deferred.promise);
doesUserExistPostReqHandler = $httpBackend.when('POST', '/doesUserExist').respond(deferred.promise);
loginPostReqHandler = $httpBackend.when('POST', '/login').respond(deferred.promise);
loginModal = $injector.get('loginModal');
}));
afterEach(function () {
$httpBackend.flush();
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
describe('AuthCtrl.login()', function () {
it('should call auth.login() with $scope.user', function () {
$scope.user = {
email: 'bwayne#wayneenterprise.com',
password: 'password123'
};
spyOn(auth, 'login').and.returnValue(deferred.promise);
$scope.login();
deferred.resolve();
$scope.$digest();
expect(auth.login).toHaveBeenCalledWith($scope.user);
});
});
});
Now when I run the test I get the following error:
TypeError: $scope.$close is not a function
I suspect this error is because in code expects $scope to be set to the scope for the modal when it calls $scope.$close and in my test $scope is set to the controllers scope. Although I'm not sure how to reference the $modal's scope.
Update: I just discovered if I add $scope.$close = function () {}; inside the it() block then the test runs properly. Is this the correct approach?
I think you are probably trying to test too much. If you need to be making all those $http calls and such just to test a controller then you are almost certainly doing something wrong.
This is how I would go about testing your controller. See comments for further explanation. I realise this might not fit you use-case exactly but hopefully you will find it helpful to see a different approach.
DEMO
appSpec.js
describe('Auth Controller Tests', function () {
var $scope, $controller, $state, auth, controller,
loginDeferred, $closeSpy, goSpy, loginSpy;
beforeEach(module('enigmaApp'));
beforeEach(inject(function($q, _$controller_, _$rootScope_){
$controller = _$controller_;
$scope = _$rootScope_.$new();
loginDeferred = $q.defer();
// create spies
$closeSpy = jasmine.createSpy('$close');
goSpy = jasmine.createSpy('go');
loginSpy = jasmine
.createSpy('login')
.and
.returnValue(loginDeferred.promise);
// create mock services with spies
$scope.$close = $closeSpy;
auth = {
login : loginSpy
};
$state = {
go: goSpy
}
// initiate controller and inject mocks
controller = $controller('AuthCtrl', {
$scope: $scope,
auth: auth,
$state: $state
});
// manual $digest to update our controller
// with our mocked services and scope
$scope.$digest();
}));
describe('AuthCtrl.login()', function () {
it('should call auth.login() with $scope.user', function () {
// define mock user object on our $scope
$scope.user = {
email: 'bwayne#wayneenterprise.com',
password: 'password123'
};
// call login() which in turn calls our
// loginSpy
$scope.login();
// just assert that our loginSpy was called with
// the mockUser
// we don't care about anything else so no need
// to worry about promises etc.
expect(auth.login).toHaveBeenCalledWith($scope.user);
});
it('should call $state.go on succesful login', function(){
// call login which will
// call our authLogin spy that returns
// the loginDeferred promise
$scope.login();
// manually resolve the loginDeferred promise and
// call $digest to trigger the then() callback
loginDeferred.resolve({});
$scope.$digest();
// assert $state.go is called when
// our then callback it triggered.
expect($state.go).toHaveBeenCalledWith('secure.user');
});
it('should set the errMsg to true if the login fails', function(){
expect($scope.hasErrMsg).toBeUndefined();
$scope.login();
// this time reject our promise
// so we can evaluate the catch callback
loginDeferred.reject({});
$scope.$digest();
expect($scope.hasErrMsg).toBe(true);
});
});
});
app.js
var app = angular.module('enigmaApp', ['ui.router', 'ui.bootstrap']);
app.controller('AuthCtrl', function($scope, auth, $state){
// Warning: OPINIONATED CODE
// I refactored your auth login function
// to use the then and catch methods which I
// think are much cleaner
$scope.login = function() {
auth
.login($scope.user)
.then(function(response) {
$scope.$close(response);
$state.go('secure.user');
})
.catch(function(response) {
$scope.hasErrMsg = true;
$scope.errMsg = 'Incorrect password.';
$scope.$dismiss;
});
};
});

Testing Angular controllers using controllerAs

I've been slowly updating an angular app ready for a 2.0 migration when it's released and I'm running into issues updating my specs. The main problem is I've started to use the controller as syntax and remove scope from my controllers completely but now I'm having problems spying on services that are called in the context of 'this' inside the controller.
I'm copying below the controller code and the spec. I've dummed it down so we can try and solve this problem easily.
Controller:
var LoginCtrl;
LoginCtrl = (function() {
function LoginCtrl(Auth, $state, Session) {
this.Auth = Auth;
this.$state = $state;
this.Session = Session;
this.credentials = {
username: undefined,
password: undefined,
email: undefined
};
}
LoginCtrl.prototype.login = function () {
var _self = this;
this.Auth.login(this.credentials).then(function(response) {
console.log(response)
}, function(error) {
console.log(error)
});
};
return LoginCtrl;
})();
angular.module('projectx').controller('LoginCtrl', ['Auth', '$state', 'Session', LoginCtrl]);
Spec:
describe('Controller: LoginCtrl', function () {
var $controller, Auth;
beforeEach(module('projectx'));
beforeEach(inject(function(_$controller_, _Auth_) {
$controller = _$controller_;
Auth = _Auth_;
}));
it('should pass user credentials to server to log you in', function() {
var scope = {};
var login = $controller('LoginCtrl as login', {$scope: scope});
expect(scope.login).toBe(login);
spyOn(scope.login.Auth, 'login');
scope.login.credentials = {
username: 'test',
password: 'test'
};
scope.login.login();
expect(scope.login.Auth.login).toHaveBeenCalledWith({username: 'test', password: 'test'});
});
});
The error I get is 'undefined' when the controller trys to call this.Auth.login because my spec is spying on (scope.login.Auth, 'login') which is a separate instance I think? Heres the actual error:
PhantomJS 1.9.8 (Mac OS X) Controller: LoginCtrl should pass user credentials to server to log you in FAILED
TypeError: 'undefined' is not an object (evaluating 'this.Auth.login(this.credentials).then')
at /app/controllers/login.js:25
at /spec/controllers/login.js:49

Angularjs unit test app configuration

I am developing angularjs app and i have defined app configuration data. When i unit test my controller i am getting error if i remove app configuration unit tests are running with out errors.
My code is here
app.js
var signup = angular.module('Signup', []);
signup.config("ajaxURL",{"signupSubmit": "/signup/submit","signupCheckEmailAvailability": "/signup/checkemail"});
signup.factory('SignupService',['$http','ajaxURL',function($http,URL){
return {
submit: function(signupData){
console.log("in submit service--");
console.log(signupData);
var promise = $http.post((URL.signupSubmit).toString(), signupData).then(function(response){
return response;
});
return promise;
},
checkEmailAvailability: function(emailData){
var promise = $http.post((URL.signupCheckEmailAvailability).toString(),emailData).then(function(response){
return response;
});
return promise;
}
};
}]);
signup.controller('SignupCtrl',['SignupService', '$scope',function(Signup, $scope){
$scope.signupPromise;
$scope.submitSignupForm = function(signupData){
$scope.signupPromise = Signup.submit(signupData);
signupSubmitEvent();
}
function signupSubmitEvent(){
$scope.signupPromise.then(function(response){
console.log("http response");
});
}
}]);
appSpec.js
'use strict';
describe('signup unit tests', function() {
var signup, scope, $httpBackend, ctrl;
var userData = {"userid":"2","email":"xxxx#sss.com","clientId":"123456789","clientSecret":"a1b2c3d4e5f6","accessToken":"AP16MD3217"};
beforeEach(module('Signup'));
beforeEach(inject(function($injector, _$rootScope_, $controller, _SignupService_){
$httpBackend = $injector.get('$httpBackend');
scope = _$rootScope_.$new();
var Signup = _SignupService_;
ctrl = $controller('SignupCtrl', {Signup: Signup, $scope: scope});
}));
it("simple testing", function(){
console.log("in simple test");
$httpBackend.expectPOST("/signup/submit/", userData).respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}]);
$httpBackend.flush();
scope.submitSignupForm(userData);
});
});
Can any on help me how to add configuration data to the unit test?
If you want test your services/controllers without running the config section of your app you need to put the controllers and services into a separate module. e.g.:
var signup = angular.module('Signup', ['Signup.services', 'Signup.ctrls']);
signup.config("ajaxURL",{"signupSubmit": "/signup/submit","signupCheckEmailAvailability": "/signup/checkemail"});
var signupServices = angular.module('Signup.services', []);
signupServices.factory('SignupService',['$http','ajaxURL',function($http,URL){
return {
...
};
}]);
var signupControllers = angular.module('Signup.ctrls', ['Signup.services']);
signupControllers.controller('SignupCtrl',['SignupService', '$scope',function(Signup, $scope){
...
}]);
Then in your test you can just load up the module you want to test. e.g.:
beforeEach(module('Signup.services'));

Resources