Confused by angular unit test - angularjs

I'm testing out on of my controllers. I keep getting
Error: Expected POST /auth/signup with different data
EXPECTED: {"username":"justin","firstName":"Justin","lastName":"Young","email":"xxxx#xxx.com","company":"5579d602ba9f26a414be5d57","url":"http://www.me.com","referrer":"me#me.com"}
It's completing the post to auth/signup as expected, but the data is empty? I'm passing in sampleUserResponse into the expectations so I don't get why it's not passing that data back as the response. What am I doing wrong?
My test:
'use strict';
(function() {
// Authentication controller Spec
describe('AdminItemController', function() {
// Initialize global variables
var controller,
scope,
$httpBackend,
$stateParams,
$location;
beforeEach(function() {
jasmine.addMatchers({
toEqualData: function(util, customEqualityTesters) {
return {
compare: function(actual, expected) {
return {
pass: angular.equals(actual, expected)
};
}
};
}
});
});
// Load the main application module
beforeEach(module(ApplicationConfiguration.applicationModuleName));
// The injector ignores leading and trailing underscores here (i.e. _$httpBackend_).
// This allows us to inject a service but then attach it to a variable
// with the same name as the service.
beforeEach(inject(function($controller, $rootScope, _$location_, _$stateParams_, _$httpBackend_) {
// Set a new global scope
scope = $rootScope.$new();
// Point global variables to injected services
$stateParams = _$stateParams_;
$httpBackend = _$httpBackend_;
$location = _$location_;
// Initialize the Authentication controller
controller = $controller('AdminItemController', {
$scope: scope,
$stateParams:$stateParams
});
$httpBackend.when('GET', 'modules/core/views/home.client.view.html').respond({});
}));
it('$scope.create() with valid form data should send a POST request with the form input values and then locate to new object URL', inject(function(Users) {
// Create a sample article object
var sampleUserPostData = new Users({
username: 'justin',
firstName:'Justin',
lastName:'Young',
email:'xxxx#xxx.com',
company:'5579d602ba9f26a414be5d57',
url:'http://www.me.com',
referrer:'me#me.com'
});
// Create a sample User response
var sampleUserResponse = new Users({
_id:'4579d602ba9f26a414be5d59',
username: 'justin',
firstName:'Justin',
lastName:'Young',
email:'xxxx#xxx.com',
company:'5579d602ba9f26a414be5d57',
url:'http://www.me.com',
referrer:'me#me.com'
});
// Fixture mock form input values
//scope.title = 'An User about MEAN';
//scope.content = 'MEAN rocks!';
// Set POST response
$httpBackend.expectPOST('/auth/signup', sampleUserPostData).respond(sampleUserResponse);
// Run controller functionality
scope.addPost();
$httpBackend.flush();
// Test form inputs are reset
//expect(scope.title).toEqual('');
//expect(scope.content).toEqual('');
// Test URL redirection after the User was created
//expect($location.path()).toBe('/admin/users/' + sampleUserResponse._id);
}));
});
}());
My Simplified Controller:
.controller('AdminItemController', ['$scope', '$http', '$location','apiResource','$stateParams', '$state','$log',
function($scope, $http, $location, apiResource, $stateParams, $state, $log) {
$scope.addPost = function() {
apiResource.save({api_resource:'auth', api_action: 'signup'},$scope.item).$promise.then(function(response){
$scope.$parent.users.push($scope.item);
});
};
}
])

The problem is in your controller, the params you send with the POST is $scope.item but in your test, you DO NOT set your $scope.item to be anything. Therefore, a POST with undefined params will be sent (because $scope.item is undefined). Moreover, in your test, you expect the params sent to equal to sampleUserPostData. Apparently it will fail because undefined !== sampleUserPostData. What you can do is just to set the scope.item = sampleUserPostData; before expectPOST and it will be fine.
Working fiddle.

Related

How to test $scope in an AngularJS controller

How do I test the $scope object of my controller?
Am I able to get the actual data that was attached to it?
I have my test set up as shown below, but myScope is saying undefined.
'use strict';
describe('myApp.view1 module', function() {
var $httpBackend, $rootScope, createController, jsonHandler;
beforeEach(module('myApp.view1'));
describe('view1 controller', function(){
beforeEach(inject(function($rootScope, $controller, $injector) {
// Set up the mock http service responses
$httpBackend = $injector.get('$httpBackend');
// backend definition common for all tests
$httpBackend.when('GET', 'view1/quizzes.json')
.respond({data: '[XXX,XXX,XXX]'});
// Get hold of a scope (i.e. the root scope)
$rootScope = $injector.get('$rootScope');
// The $controller service is used to create instances of controllers
var $controller = $injector.get('$controller');
var myScope = $rootScope.$new()
createController = function() {
return $controller('View1Ctrl', {'$scope' : myScope });
};
var controller = createController();
}));
it('should get the json files', function(){
$httpBackend.expectGET('/view1/quizzes.json');
});
it('should get the json files', function(){
expect(myScope).toBe("words");
});
});
});
controller:
myApp.controller('View1Ctrl', [
'$scope',
'$http',
function($scope, $http) {
$http.get('view1/quizzes.json')
.then(function(res){
$scope.quizzes = res.data.quizzes
})
.then(function(){
$http.get('view1/questions.json')
.then(function(res){
$scope.questions = res.data.questions
})
.then(function(){
$scope.quiz = [ [], [], [], [], [] ]
_($scope.questions).forEach(function(qu){
_($scope.quizzes).forEach(function(quiz){
if (_.includes($scope.quizzes[(quiz.id - 1)].question_ids, qu.id)){
$scope.quiz[(quiz.id - 1)].push(qu)
}
})
})
})
.then(function(){
// console.log($scope.quiz)
})
});
}
]);
OK. First let's start by cleaning up the mess a little. You don't need to use $injector to get access to services, since they can be injected using inject.
You also don't want a single test for a whole module. Your test should just test the controller. I'll show you an example testing just the first interaction with the backend.
describe('view1 controller', function() {
// declare the variables that need to be used in all tests
// the $scope contains the data and functions we want to test
var $scope;
// the $httpBackend service allows mocking the http... backend
var $httpBackend;
// the $controller service allows instantiating our controller
var $controller;
// load the module containing the component I want to test
beforeEach(module('myApp.view1'));
// prepare the tests. Use inject to access the services we need.
// To avoid a name clash with the variables defined above, we can
// enclose the actual service names into underscores
beforeEach(inject(function($rootScope, _$httpBackend_, _$controller_) {
// initialize our variables
$httpBackend = _$httpBackend_;
$controller = _$controller_;
// create a scope and initialize our $scope variable with it
$scope = $rootScope.$new();
}));
// we need to be able to instantiate our controller in our tests. Let's
// define a function that does that
function createController() {
// we initialize the controller with the scope we have created sooner
// so, the scope the controller receives as argument is our scope
// the controller will populate the scope, and we can test it has
// populated it correctly
$controller('View1Ctrl', { $scope: $scope });
}
// now let's write a simple test. The controller, when instantiated,
// should use $http to load view1/quizzes.json, and, once it gets
// the response, it should populate $scope.quizzes with the quizzes
// attribute of the JSON received as response
it('should populate the quizzes from the backend', function() {
// we first need to tell our fake backend: you should receive a
// request, and when you do, you should return this response
var data = {
quizzes: ['hello world']
};
$httpBackend.expectGET('view1/quizzes.json').respond(data);
// now we will create our controller. The controller should send
// a http request to get the quizzes. If it indeed does correctly,
// the fake backend will return the above data **when we tell it to
// do it**.
createController();
// Now, the controller has sent the request. But it hasn't received
// the response yet. Let's send the response.
$httpBackend.flush();
// now, the controller should have received the response, and should
// thus have extracted the quizzes from it and populated
// $scope.quizzes with them
expect($scope.quizzes).toEqual(data.quizzes);
});
});

TypeError: 'undefined' is not a function (evaluating '$scope.$dismiss()')

I need to test my signin process using karma unit testing, but I am stuck with an error while executing the test.
My controller code for login:
$scope.signin = function (valid) {
if (valid) {
$http.post('/auth/signin', $scope.credentials).success(function (response) {
console.log(response)
// If successful we assign the response to the global user model
$scope.authentication.user = response.user;
localStorage.setItem('token',response.token.logintoken);
// And redirect to the index page
$scope.$dismiss();
}).error(function (response) {
$scope.error = response.message;
});
} else {
$scope.userSigninrequredErr = true;
}
};
Karma test code:
(function () {
// Authentication controller Spec
describe('AuthenticationController', function () {
// Initialize global variables
var AuthenticationController,
scope,
$httpBackend,
$stateParams,
$cookieStore,
notify,
$location,
modal;
beforeEach(function () {
jasmine.addMatchers({
toEqualData: function (util, customEqualityTesters) {
return {
compare: function (actual, expected) {
return {
pass: angular.equals(actual, expected)
};
}
};
}
});
});
// Load the main application module
beforeEach(module(ApplicationConfiguration.applicationModuleName));
// The injector ignores leading and trailing underscores here (i.e. _$httpBackend_).
// This allows us to inject a service but then attach it to a variable
// with the same name as the service.
beforeEach(inject(function ($controller, $rootScope, _$location_, _$stateParams_, _$httpBackend_, _$cookieStore_, _notify_) {
// Set a new global scope
scope = $rootScope.$new();
// Point global variables to injected services
$stateParams = _$stateParams_;
$httpBackend = _$httpBackend_;
$cookieStore = _$cookieStore_;
notify = _notify_;
$location = _$location_;
modal = jasmine.createSpyObj('modal', ['show', 'hide']);
// Initialize the Authentication controller
AuthenticationController = $controller('AuthenticationController', {
$scope: scope
});
}));
it('$scope.signin() should login with a correct user and password', function () {
// Test expected GET request {user: 'Fred', token: {logintoken: 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9'}}
scope.credentials.user = 'jenson';
scope.credentials.token = {logintoken: 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9'};
$httpBackend.when('POST', '/auth/signin').respond(200, scope.credentials);
scope.signin(true);
$httpBackend.when('GET', /\.html$/).respond('');
$httpBackend.flush();
// Test scope value
expect(scope.credentials.user).toEqual('jenson');
expect($location.url()).toEqual('/dashboard');
});
}
I'm getting the correct response after executing karma test but the following error is showing in the devtools console.
TypeError: 'undefined' is not a function (evaluating '$scope.$dismiss()')
Any help is appreciated. Thanks.
So the problem is that in "real" code, the modal service is invoking the controller and creating a new scope for you, populating the $dismiss() method on it, but in your unit test, you are invoking the controller manually (using the $controller service), meaning there is no $scope.$dismiss() function, hence the error.
Instead, you need to either do what you do in the real code, and use the modal service to instantiate your controller OR at the very least, mimic what the modal service does and add some stub functions on your new scope, including $dismiss().

Jasmine doesn't execute callbacks from $resource

So I'm having this issue when writing my tests that I don't know how to solve:
This is my controller:
'use strict';
angular.module('testApp')
.controller('SettingsExtrasCtrl', function ($scope, $log, Auth, Property, $modal, dialogs, growl) {
$scope.deleteExtra = function(index) {
var dlg = dialogs.confirm('Please Confirm', 'Are you sure you want to delete '+$scope.selectedProperty.extras[index].name+'?');
dlg.result.then(function() {
Property.removeExtra({ _id : $scope.selectedProperty._id, otherId : $scope.selectedProperty.extras[index]._id }, function(res) {
$scope.selectedProperty.extras.splice(index,1);
growl.success("Success message", {title : 'Success'});
},
function(err) {
console.log(err);
});
});
};
});
$scope.selectedProperty comes from a parent controller.
And here is my test:
'use strict';
describe('Controller: SettingsExtrasCtrl', function () {
// load the controller's module
beforeEach(module('testApp'));
var SettingsExtrasCtrl, scope, stateParams, Property, httpBackend;
var dialogs = {
confirm: function (title, message) {
return {
result: {
then: function (callback) {
return callback();
}
}
}
}
};
var fakeProperty = {
_id : 'propertyId',
extras : [
{
_id : 'extraId',
name : 'Extra'
}
]
};
beforeEach(inject(function ($controller, $rootScope, _Property_, _$httpBackend_, $state, $modal, _dialogs_) {
scope = $rootScope.$new();
scope.selectedProperty = fakeProperty;
stateParams = {propertyId: fakeProperty._id};
Property = _Property_;
httpBackend = _$httpBackend_;
spyOn(Property, 'removeExtra');
spyOn(_dialogs_, 'confirm').andCallFake(dialogs.confirm);
SettingsExtrasCtrl = $controller('SettingsExtrasCtrl', {
$scope: scope,
$stateParams: stateParams,
dialogs: _dialogs_,
$state: $state
});
}));
it('should delete an extra', inject(function(_dialogs_) {
httpBackend.expectDELETE('/api/properties/' + stateParams.propertyId + '/extras/someextraId').respond(200, '');
scope.deleteExtra(0);
expect(_dialogs_.confirm).toHaveBeenCalled();
expect(Property.removeExtra).toHaveBeenCalled();
expect(scope.selectedProperty.extras.length).toBe(0);
}));
});
The assert expect(scope.selectedProperty.extras.length).toBe(0); fails because expects 1 to be 0 because the success callback from Property.removeExtra is never called.
Any idea on how to solve this?
Thanks.
For promise to be executed you have to call a digest cycle :
scope.deleteExtra(0);
scope.$digest();
[EDIT]
Has it's a network call, you will have to look at $httpBackend
basically it work like that :
//you can mock the network call
$httpBackend.whenGET('https://url').respond(200);//status code or object
// stuff that make the call
....
$httpBackend.flush();
expect(thing).toBe(stuff);
A bit of doc :
The $httpBackend used in production always responds to requests asynchronously. If we preserved this behavior in unit testing, we'd have to create async unit tests, which are hard to write, to follow and to maintain. But neither can the testing mock respond synchronously; that would change the execution of the code under test. For this reason, the mock $httpBackend has a flush() method, which allows the test to explicitly flush pending requests. This preserves the async api of the backend, while allowing the test to execute synchronously.

Angular Test a controller that use RouteParams

I need to test a Controller that use $routeParams to define it action. Maybe it is a test problem, or is wrong the way I wrote the controller, so now I can't write a test.
Here is my controller
angular.module('cmmApp')
.controller('UserCtrl', function ($scope, $location, $routeParams, $ionicLoading, $ionicPopup, Userservice) {
//if a user id is set retrieve user information
if(typeof $routeParams.id !== 'undefined'){
var loader = $ionicLoading.show({content: "Retrieving user data"});
var user = Userservice.get({id: $routeParams.id}).$promise;
user.then(function(res){
console.log(res);
$scope.user = res.user;
loader.hide();
});
}
//else create an empty resource
else {
$scope.user = new Userservice;
}
});
Basically I only want to load a resource if an id is provided is $routeParams, else create an empty resource.
And here is the test:
'use strict';
describe('Controller: UserCtrl', function () {
// load the controller's module
beforeEach(module('cmmApp'));
var UserCtrl,
scope,
routeParams;
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope, $routeParams, Userservice) {
scope = $rootScope.$new();
routeParams = $routeParams;
UserCtrl = $controller('UserCtrl', {
$scope: scope
});
console.log("routeParams");
routeParams = {id: 8490394};
console.warn(routeParams);
}));
it('should create an epmty user', function () {
console.log(scope.user);
expect(scope.user).toEqual({});
});
});
When I run grunt test karma reply:
Controller: UserCtrl should create an epmty user FAILED
Expected { } to equal { }.
Error: Expected { } to equal { }.
at null.<anonymous> (/Users/carlopasqualicchio/Sites/CMM_Ionic/test/spec/controllers/user.js:28:24)
I'm wonering how to tell karma to change $routeParams.idbefore a test to run, and assign a different value in two different test (I need to set it to null in the first, and to a value to the other).
Any help is appreciated.
Thanks, Matt.
You can pass a custom $routeParams object using the second argument of $controller (locals object), i.e. the same way you pass the $scope:
UserCtrl = $controller('UserCtrl', {
$scope: scope,
$routeParams: {id: '...'}
});

Testing AngularJs' $http.defaults.headers.common if specific header is set

So I'm new to the world of JavaScript and AngularJS and therefor my code is not as good as it should be yet, but it's improving. Nevertheless I started learning and implementing a simple login page with a REST Backend. After the Login-Form is submitted, a authentication-token is returned and set as a default http-header property like this
$http.defaults.headers.common['X-AUTH-TOKEN'] = data.authToken;
This works fine whenever I test it manually, but that's not the way to go so I'd like to implement a unit-test which checks if the X-AUTH-TOKEN header is set.
Is there a way to check that with $httpBackend? e.g I have the following test:
describe('LoginController', function () {
var scope, ctrl, $httpBackend;
// Load our app module definition before each test.
beforeEach(module('myApp'));
// The injector ignores leading and trailing underscores here (i.e. _$httpBackend_).
// This allows us to inject a service but then attach it to a variable
// with the same name as the service.
beforeEach(inject(function (_$httpBackend_, $rootScope, $controller) {
$httpBackend = _$httpBackend_;
scope = $rootScope.$new();
ctrl = $controller('LoginController', {$scope: scope}, {$http: $httpBackend}, {$location: null});
}));
it('should create an authToken and set it', function () {
$httpBackend.expectPOST('http://localhost:9000/login', '200').respond(200, '{"authToken":"52d29fd63004c92b972f6b99;65e922bc-5e33-4bdb-9d52-46fc352189fe"}');
scope.login('200');
$httpBackend.flush();
expect(scope.data.authToken).toBe('52d29fd63004c92b972f6b99;65e922bc-5e33-4bdb-9d52-46fc352189fe');
expect(scope.loginValidationOverallError).toBe(false);
expect(scope.status).toBe(200);
});
My Controller looks like this:
.controller('LoginController', ['$scope', '$http', '$location',
function ($scope, $http, $location) {
// Login Stuff
$scope.data = {};
$scope.status = {};
$scope.loginValidationOverallError = false;
$scope.login = function (user) {
$http.post('http://localhost:9000/login', user).success(function (data, status) {
$scope.data = data;
$scope.status = status;
$scope.loginValidationOverallError = false;
console.log($scope.status, $scope.data);
$http.defaults.headers.common['X-AUTH-TOKEN'] = data.authToken;
$location.path('/user');
}).error(function (data, status) {
console.log(status + ' error');
$scope.loginValidationOverallError = true;
});
};
...
I checked the documentation at http://docs.angularjs.org/api/ngMock.$httpBackend but am not sure if the last test is actually applicable to my code (and how that code actually tests something)
it('should send auth header', function() {
var controller = createController();
$httpBackend.flush();
$httpBackend.expectPOST('/add-msg.py', undefined, function(headers) {
// check if the header was send, if it wasn't the expectation won't
// match the request and the test will fail
return headers['Authorization'] == 'xxx';
}).respond(201, '');
$rootScope.saveMessage('whatever');
$httpBackend.flush();
});
I was facing the same issue and I finally solved it. It was very tricky
Souce code for AuthenticationService.login() function
$http.post(...)
.success(function(data) {
...
$http.defaults.headers.common['Authorization'] = data.oauth_token;
});
Test code
beforeEach(inject(function(_$httpBackend_,AuthenticationService) {
$httpBackend = _$httpBackend_;
$authenticationService = AuthenticationService;
}));
it('should login successfully with correct parameter', inject(function($http) {
// Given
...
...
var fakeResponse = {
access_token: 'myToken'
}
$httpBackend.expectPOST('oauth/token',urlEncodedParams, function(headers) {
return headers['Content-Type'] === 'application/x-www-form-urlencoded';
}).respond(200, fakeResponse);
// When
$authenticationService.login(username,password);
// Then
$httpBackend.flush();
expect($http.defaults.headers.common['Authorization']).toBe('myToken');
The trick here is that the default header is set on the real $http service, not the mocked $httpBackend. That's why you should inject the real $http service
I've tried testing the $httpBackend but got an "undefined" error because $httpBackend does not have 'defaults' property

Resources