I make an ionic app and it finish but when i start to add tests to it I face a problem with $resources ,in this case I have this Controller :
.controller('newAccountCtrl', function($scope, $window, $rootScope, API, $ionicPopup, $state) {
$scope.newData = {};
$scope.$on('$ionicView.enter', function() {
$scope.newData = {};
});
$scope.newInfo = function() {
API.newAccountInfo().update({ restCode: $scope.newData.restore_code }, $scope.newData, function(res, header) {
$rootScope.popup('success', "OKAY");
$window.location.href = '#/login';
}, function(err) {
if (err.data == null)
$rootScope.popup("Error", "no connection");
else
$rootScope.popup('error', err.data.error);
});
}
})
and in the service i make a request using $resources in function :
angular.module('starter.services', [])
.factory('API', function($rootScope, $resource, $ionicPopup, $ionicLoading, $window) { return {
newAccountInfo: function() {
return $resource(base + '/restoreinfo/:restCode', { restCode: '#_restCode' }, {
update: {
method: 'PUT'
}
}, {
stripTrailingSlashes: false
});
}}});
and in the my test the following code:
describe('newAccountCtrl', function() {
var controller,
deferredLogup, scope, $q;
beforeEach(angular.mock.module('starter'));
// TODO: Load the App Module
beforeEach(module('starter.controllers'));
beforeEach(module('starter.services'));
// TODO: Instantiate the Controller and Mocks
beforeEach(inject(function($controller, _$q_, $rootScope, _API_) {
$q = _$q_;
scope = $rootScope.$new();
API = _API_;
spyOn(API, 'newAccountInfo').and.callThrough(function(callback) {
deferredLogup.promise.then(callback);
return { $promise: deferredLogup.promise };
});
controller = $controller('newAccountCtrl', {
'$scope': scope,
API: API
});
}));
it('#newAccountInfo', function() {
scope.newInfo();
expect(API.newAccountInfo.update).toHaveBeenCalled();
}) });
but I get the error :
Expected a spy, but got undefined.
What I misunderstand here, the code work perfect
just macke factory return the resources direct and remove the functions .
Related
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.
I'm very new to jasmine and I got some issue trying to mock a provider.
I have a provider that looks like :
angular.module('myApp')
.factory('MyService', function ($resource) {
return {
actionResource : function(projectId, actionId){
var url = 'blablabla';
if(actionId){
url += "/"+actionId;
}
return $resource(url, {}, {
'create': {
method: 'POST'
}
});
},
};
});
I have a controller using this factory
angular.module('myApp')
.controller('myCtrl', function ($scope, MyService, $state) {
$scope.addAction = function(){
MyService.actionResource($scope.projectId).create($scope.action,
function(){
$state.go('somewhere', {});
});
};
});
and I'd really like to test this controller, at the moment I'm doing something like :
'use strict';
describe('[test] [controller] - myCtrl', function() {
beforeEach(angular.mock.module('myApp'));
//mock creation
beforeEach(
module(function($provide) {
$provide.factory('MyService', function() {
var actionResource = function(projectId, actionId){
var create = function(){return {}};
return {create:create};
}
return {actionResource:actionResource}
}
)})
);
var $httpBackend, $rootScope, myController, mockMyService;
var scope = {};
beforeEach(angular.mock.inject(function($injector, $rootScope, MyService) {
var $controller = $injector.get('$controller');
scope=$rootScope.$new();
mockMyService = MyService;
spyOn(mockPilotageService, 'actionResource').and.callThrough();
spyOn(mockPilotageService.actionResource(), 'create');
createController = function() {
return $controller("myCtrl", {$scope:scope, myService:mockMyService});
};
}));
it('myCtrl - addAction() calls actionRessource.create() method', function() {
createController();
scope.addAction();
expect(mockMyService).toBeDefined();
expect(mockMyService.actionResource).toHaveBeenCalled();
expect(mockMyService.actionResource().create).toHaveBeenCalled();
});
});
and I'm getting this error :
Error: Expected a spy, but got Function.
at /Users/*****/myController.spec.js:86
So it's able to spy on mockMyService.actionResource but not on mockMyService.actionResource().create. I can't understand why
any help would be more than welcome
thanks
I have a promise in a controller that I'm trying to test and I'm getting Error: Unexpected request: POST /v1/users.
I'm trying to spyOn the AuthService.changePassword which returns a promise and test whether it got called or not. Not sure why it's actually making the POST call...
controller
angular.module('example')
.controller('ChangePasswordCtrl', ['AuthService', '$state',
function(AuthService, $state) {
var vm = this;
vm.submitted = false;
vm.submit = function(valid) {
vm.submitted = true;
if (!valid) return false;
AuthService.changePassword(vm.email)
.then(function(res) {
$state.go('reset.confirmation');
}, function(err) {
vm.hasError = true;
});
};
}
]);
unit test
describe('ChangePasswordCtrl', function() {
var ctrl, scope, AuthService, $q, $state, deferred;
beforeEach(module('example'));
function _inject() {
inject(function($controller, $rootScope, _AuthService_, _$state_, _$q_) {
scope = $rootScope.$new();
$state = _$state_;
$q = _$q_;
AuthService = _AuthService_;
ctrl = $controller('ChangePasswordCtrl', {
$scope: scope
});
});
}
describe('#submit', function() {
beforeEach(function() {
_inject();
deferred = $q.defer();
spyOn(AuthService, 'changePassword').and.returnValue(deferred.promise);
spyOn($state, 'go');
});
describe('when email address is valid', function() {
it('should call the changePassword method on the AuthService', function() {
ctrl.submit(true);
scope.$digest();
expect(ctrl.submitted).toBe(true);
expect(AuthService.changePassword).toHaveBeenCalled();
});
});
});
});
Your spec code works for me (the real implementation of AuthService.changePassword doesn't get called): http://jsfiddle.net/7W2XB/7/
angular.module('example', [])
.factory('AuthService', function() {
return {
changePassword: function() {
throw new Error('Should not be called');
}
};
})
.controller('ChangePasswordCtrl', ['AuthService',
function(AuthService) {
var vm = this;
vm.submitted = false;
vm.submit = function(valid) {
vm.submitted = true;
if (!valid) return false;
AuthService.changePassword(vm.email)
.then(function(res) {
$state.go('reset.confirmation');
}, function(err) {
vm.hasError = true;
});
};
}
]);
describe('ChangePasswordCtrl', function() {
var ctrl, scope, AuthService, $q, deferred;
function _inject() {
module('ui.router');
module('example');
inject(function($controller, $rootScope, _AuthService_, _$state_, _$q_) {
scope = $rootScope.$new();
$state = _$state_;
$q = _$q_;
AuthService = _AuthService_;
ctrl = $controller('ChangePasswordCtrl', {
$scope: scope
});
});
}
describe('#submit', function() {
beforeEach(function() {
_inject();
deferred = $q.defer();
spyOn(AuthService, 'changePassword').and.returnValue(deferred.promise);
});
describe('when email address is valid', function() {
it('should call the changePassword method on the AuthService', function() {
ctrl.submit(true);
scope.$digest();
expect(ctrl.submitted).toBe(true);
expect(AuthService.changePassword).toHaveBeenCalled();
});
});
});
});
Some questions that might help make the JSFiddle more realistic to your situation: What versions of angular and Jasmine are you using? - How are you defining the AuthService (presumably using angular.factory)?
My controller:
angularMoonApp.controller('SourceController', ['$scope', '$rootScope', '$routeParams', 'fileService', function ($scope, $rootScope, $routeParams, fileService) {
$scope.init = function() {
$rootScope.currentItem = 'source';
fileService.getContents($routeParams.path).then(function(response) {
$scope.contents = response.data;
$scope.fileContents = null;
if(_.isArray($scope.contents)) {
// We have a listing of files
$scope.breadcrumbPath = response.data[0].path.split('/');
} else {
// We have one file
$scope.breadcrumbPath = response.data.path.split('/');
$scope.breadcrumbPath.push('');
$scope.fileContents = atob(response.data.content);
fileService.getCommits(response.data.path).then(function(response) {
$scope.commits = response.data;
});
}
});
}
$scope.init();
}]);
My unit test:
(function() {
describe('SourceController', function() {
var $scope, $rootScope, $httpBackend, $routeParams, $q, createController, fileService, deferred;
beforeEach(module('angularMoon'));
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
$rootScope = $injector.get('$rootScope');
$routeParams = $injector.get('$routeParams');
$scope = $rootScope.$new();
$q = $injector.get('$q');
deferred = $q.defer();
fileService = $injector.get('fileService');
var $controller = $injector.get('$controller');
createController = function() {
return $controller('SourceController', {
'$scope': $scope,
'$routeParams': $routeParams,
'fileService': fileService
});
};
}));
it("should set the current menu item to 'source'", function() {
createController();
$scope.init();
expect($rootScope.currentItem).toBe('source');
});
it("should get test the getContents call of the fileService", function() {
spyOn(fileService, 'getContents').andCallThrough();
createController();
$scope.init();
expect(fileService.getContents).toHaveBeenCalled();
});
it("should return an object with multiple files", function() {
var multipleFiles = [{path: '.DS_Store'}, {path: '.bowerrc'}];
deferred.resolve(multipleFiles);
spyOn(fileService, 'getContents').andReturn(deferred.promise);
createController();
$scope.init();
expect($scope.contents).toBe(multipleFiles);
expect($scope.breadcrumbPath).toBe('');
});
});
})();
The last test fails with:
Expected undefined to be [ { path : '.DS_Store' }, { path : '.bowerrc' } ].
Expected undefined to be ''.
Why is the $scope undefined here?
Your controller is expecting you to inject in $rootScope which you are not doing in your unit test.
You have:
createController = function() {
return $controller('SourceController', {
'$scope': $scope,
'$routeParams': $routeParams,
'fileService': fileService
});
But but should have:
createController = function() {
return $controller('SourceController', {
'$scope': $scope,
'$rootScope': $rootScope,
'$routeParams': $routeParams,
'fileService': fileService
});
Also, you will want to call this code:
createController();
$scope.init();
before you resolve your promise:
deferred.resolve(multipleFiles);
The scope is not undefined. What is undefined is $scope.contents and $scope.breadcrumbPath.
And that's because promise callbacks are always being called asynchronously. You need to call
$scope.$apply()
before verifying your expectations.
I have a angularjs web application and want to use qunit for unit testing in it. I have a controller:
function RootCtrl($scope, $rootScope, $window, $location) {
// logger is empty at the start
$scope.logger = '';
// we have no login error at the start
$scope.login_error = '';
//
// Get values array of object
//
$rootScope.values = function (obj) {
var vals = [];
for( var key in obj ) {
if(key !== '$$hashKey' && key !== 'checked')
vals.push(obj[key]);
}
return vals;
}
}
Now i want to write unit test for values function with qunit. I included all js files to the test/index.html and qunit.css. Now my test.js has following content:
var injector = angular.injector(['ng', 'myApp']);
var init = {
setup : function () {
this.$scope = injector.get('$rootScope').$new();
}
}
module('RootCtrl', init);
test('RootCtrl', function(){
var $controller = injector.get('$controller');
$controller('RootCtrl', {
$scope : this.$scope,
$location : this.$location
});
equal(['value'], $controller.values({'key' : 'value'}))
});
But i'm getting error: http://docs.angularjs.org/error/$injector/unpr?p0=$rootElementProvider%20%3C-%20$rootElement%20%3C-%20$location%20%3C-%20$route at:
$controller('RootCtrl', {
$scope : this.$scope,
$location : this.$location
});
How to inject correctly controller and use $scope, $rootScope, $location and another services from it?
Thank you.
Try this instead of your controller
$controller('RootCtrl',['$scope', '$rootScope', '$location','$route', function ($scope, $rootScope, $location, $route) {
$scope : this.$scope,
$location : this.$location
}]);
Had similar problem, so since no other answer here.
I ended up using:
client side code:
var myApp= angular.module('myApp', []);
myApp.controller('myCtrl', function ($scope) {
//angular client side code
$scope.canSubmit = function () {
//some logic
return true;
}
}
Qunit tests:
var ctrl, ctrlScope, injector;
module("Testing the controller", {
setup: function () {
angular.module('myApp');
injector = angular.injector(['ng', 'myApp']);
ctrlScope = injector.get('$rootScope').$new();
ctrl = injector.get('$controller')('myCtrl', { $scope: ctrlScope });
ctrlScope.model = {
//model object
};
},
teardown: function () {
}
});
test("Given something happened then allow submit", function () {
ok(ctrlScope.someFunction(...), "some functionality happened");
equal(true, ctrlScope.canSubmit());
});
This blog post was useful.
One can easily inject more into the controller under test.