How to mock $scope.variables in jasmine - angularjs

I have the following test case CompanyCtrlSpec.js
describe('ViewCompanyCtrl', function () {
var $rootScope, scope, $controller , $q ;
beforeEach(angular.mock.module('MyApp'));
beforeEach(inject(function ($rootScope, $controller ) {
scope = $rootScope.$new();
createController = function() {
return $controller('ViewCompanyCtrl', {
$scope: scope,
company : {}
});
};
}));
it('the company type should be equal to an object', function () {
var controller = new createController();
//some assertion
});
});
Following is ViewCompanyCtrl.js file
angular.module('MyApp').controller('ViewCompanyCtrl',
function ($scope, companyService, $state, meetingService, company, attachmentService) {
'use strict';
$scope.company = company;
$scope.companyInfo = {};
$scope.companyInfo['AName'] = [$scope.company.Address.Street, $scope.company.Address.ZipCode + ' ' + $scope.company.Address.City].join(', ');
//more code
});
Following is the app.routes.js file where company is getting resolved
.state('company', {
abstract: true,
url: '/company/:companyId',
resolve: {
company: function($q, $stateParams, companyService){
var deferred = $q.defer();
companyService
.getCompany($stateParams.companyId)
.error(function(data, status, headers){
//more code
})
.success(function(data){
deferred.resolve(data);
});
return deferred.promise;
}
},
My problem is that i get the following error
TypeError: $scope.company.Address is undefined in C:/Users/MyApp/WebApiRole/app/compan
y/ViewCompanyCtrl.js (line 8)
#C:/Users/MyApp/WebApiRole/app/company/ViewCompanyCtrl.js:8:42
I am guessing that this happens because i didn't mock the scope.company.Address in my test case . I am not sure how to do that . Appreciate it if Any one can help me with this , or any method to do this ?

It looks to me like the $scope.company is the same as the company that is injected into your controller. So you need only to set an Address on the company that you are injecting into your mock, like so:
beforeEach(inject(function ($rootScope, $controller ) {
scope = $rootScope.$new();
createController = function() {
return $controller('ViewCompanyCtrl', {
$scope: scope,
company : {
Address: {/* address data goes here */}
}
});
};
}));
If you want the company data to be different for each test, simply pass it into your createController() function:
beforeEach(inject(function ($rootScope, $controller ) {
scope = $rootScope.$new();
createController = function(company) {
return $controller('ViewCompanyCtrl', {
$scope: scope,
company : company
});
};
}));
it('the company type should be equal to an object', function () {
var company = {Address: {/* address data goes here */}};
var controller = new createController(company);
//some assertion
});

Try to add the controller to the definition of your route. A controller is not a singleton like the other service. It is usually tied to a state or a view.
.state('company', {
abstract: true,
url: '/company/:companyId',
controller: 'ViewCompanyCtrl'
resolve: {
company: function($q, $stateParams, companyService){
var deferred = $q.defer();
companyService
.getCompany($stateParams.companyId)
.error(function(data, status, headers){
//more code
})
.success(function(data){
deferred.resolve(data);
});
return deferred.promise;
}
},
Even better I would use the controller As convention instead of using scope in your controller. Then your controller will be come the scope.
Aside for that I would highly recommend having a look at John Papa's coding standard or Todd Motto's. Both are good and will recommend to use that convention.

Related

Unsure on how to successfully test this function using $httBackend

This is the function in the controller:
var vm = this;
vm.getData = getData;
function getData(val) {
return $http.get('/get-data', {
params: {
query: val
}
}).then(function(response) {
return response.data;
});
}
and this is my (stripped down) test file:
describe('Controller: MyCtrl', function() {
'use strict';
var MyCtrl;
var rootScope;
var scope;
var httpMock;
beforeEach(function() {
module('MyModule');
inject(function($controller, $rootScope, $httpBackend) {
rootScope = $rootScope;
scope = $rootScope.$new();
httpMock = $httpBackend;
MyCtrl = $controller('MyCtrl as vm', {
$rootScope: rootScope,
$scope: scope,
$http: httpMock,
});
});
});
describe('vm.getData()', function() {
it('returns the required data', function() {
httpMock.when('GET', '/get-data?query=test-val').respond(200, {data: 'test-data'});
httpMock.flush();
expect(scope.vm.getData('test-val')).toEqual('test-data');
});
});
});
I would like to test that the result if calling getData() return the correct data.
Currently I'm getting the error $http.get is not a function. Setting a breakpoint in my function shows that http is stubbed with $httpBackend though.
I think there is something fundamental I'm not grasping - any pointers would be greatly appreciated.
You shouldn't have to create the controller with:
$http: $httpBackend
to mock the backend. $httpBackend will mock the request itself already.
Further the test and assertion is done in the wrong order:
httpMock.when('GET', '/get-data?query=test-val').respond(200, {data: 'test-data'});
MyCtrl.getData('test-val').then(function(_result_){ //perform the request
result = _result_; //save the result of the promise
});
httpMock.flush(); //execute the request
expect(result).toBe('test-data'); //assert that the result is as expected

Unit test controller

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 .

How to invoke spyOn on a scope function

I have the following jasmine spec.
describe('ViewMeetingCtrl', function () {
var $rootScope, scope, $controller , $q ;
beforeEach(angular.mock.module('MyApp'));
beforeEach(inject(function ($rootScope, $controller ) {
scope = $rootScope.$new();
createController = function() {
return $controller('ViewMeetingCtrl', {
$scope: scope,
meeting : {}
});
};
}));
it('the meeting type should be equal to an object', function () {
var controller = new createController();
//some assertion
});
});
Following is my ViewMeetingCtrl.js
(function () {
'use strict';
angular.module('MyApp').controller('ViewMeetingCtrl', ViewMeetingCtrl);
ViewMeetingCtrl.$inject = ['$scope', '$state', '$http', '$translate', 'notificationService', 'meetingService', '$modal', 'meeting', 'attachmentService'];
function ViewMeetingCtrl($scope, $state, $http, $translate, notificationService, meetingService, $modal, meeting, attachmentService) {
$scope.meeting = meeting;
$scope.cancelMeeting = cancelMeeting;
function cancelMeeting(meetingId, companyId) {
meetingService.sendCancelNotices(companyId, meetingId)
.success(function () {
$state.go('company.view');
});
}
//more code
}
})();
My question that how do i invoke the spyOn (or any other jasmine spies related method) method on the above cancelMeeting() so that i can mock the method calls , returns etc. I did the following
describe('ViewMeetingCtrl', function () {
var $rootScope, scope, $controller , $q ;
beforeEach(angular.mock.module('MyApp'));
beforeEach(inject(function ($rootScope, $controller ) {
scope = $rootScope.$new();
createController = function() {
return $controller('ViewMeetingCtrl', {
$scope: scope,
meeting : {}
});
};
}));
it('the meeting type should be equal to an object', function () {
spyOn(scope, 'cancelMeeting');//cancelMeeting is inside the scope so did like this
var controller = new createController();
});
});
but i get the following output
Firefox 37.0.0 (Windows 8.1) ViewMeetingCtrl the meeting type should be equal to an object FAILED
Error: cancelMeeting() method does not exist in C:/Users/Work/MyApp/Tests/node_mo
dules/jasmine-core/lib/jasmine-core/jasmine.js (line 1895)
is the way i am invoking spyOn is wrong or any another syntax's am i missing ?. Or do i missing something fundamental here ?
The cancelMeeting function is not added to the scope until the controller is created. So I think you just need to reverse the lines in your test code:
it('the meeting type should be equal to an object', function () {
var controller = new createController();
spyOn(scope, 'cancelMeeting');
});
Your test code looks good. I think you just have to switch the order of your assignments. First define cancelMeeting and than assign it.
function cancelMeeting(meetingId, companyId) {
meetingService.sendCancelNotices(companyId, meetingId)
.success(function () {
$state.go('company.view');
});
}
$scope.cancelMeeting = cancelMeeting;
Or just:
$scope.cancelMeeting = function(meetingId, companyId) {
meetingService.sendCancelNotices(companyId, meetingId)
.success(function () {
$state.go('company.view');
});
}

Mock backend server (Google Forms) in AngurJS

I am using AngularJs controller to send data from form to Google Sheet. Using Jasmine I wrote unit-test, which raises the bellow issue:
Error: Unsatisfied requests: POST http://localhost:5000/google-form
at Function.$httpBackend.verifyNoOutstandingExpectation
(.../angular-mocks/angular-mocks.js:1474:13)
After googling and going through some questions in stackowerflow, I decided to post the question as I didn't find a solution for it.
Here is the code for your references:
Angular Controller
/* global $ */
'use strict';
angular.module('myApp')
.controller('QuickMessageCtrl', ['$scope', function ($scope) {
$scope.quickMessageButtonText = 'Send';
$scope.quickMessage = {
name: '',
email: '',
content: '',
};
function setSubmittingIndicators() {
$scope.quickMessageButtonText = '';
$scope.submitting = true;
}
$scope.postQuickMessageToGoogle = _.throttle(function() {
setSubmittingIndicators();
$.ajax({
url: 'https://docs.google.com/forms/d/MyFormKey/formResponse',
data: {
'entry.3' : $scope.quickMessage.name,
'entry.1' : $scope.quickMessage.email,
'entry.0' : $scope.quickMessage.content
},
type: 'POST',
dataType: 'jsonp',
statusCode: {
200: function (){
//show succes message;
}
}
});
}, 500);
}]);
Unit Test Code
'use strict';
describe('Controller: QuickMessageCtrl', function() {
var $httpBackend, $rootScope, $controller, scope, apiUrl;
beforeEach(module('myApp'));
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
apiUrl = $injector.get('apiUrl');
$httpBackend.expect(
'POST',
apiUrl + 'google-form',
{'name': 'test', 'email': 'test#test.com', 'content': 'this is content'}
).respond(200);
$rootScope = $injector.get('$rootScope');
scope = $rootScope.$new();
$controller = $injector.get('$controller');
$controller('QuickMessageCtrl', { $scope: scope });
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
describe('Successful form submit', function() {
beforeEach(function() {
scope.quickMessageForm = { $valid: true };
scope.quickMessage.email = 'test#test.com';
scope.quickMessage.name = 'test';
scope.quickMessage.content = 'this is test';
scope.postQuickMessageToGoogle();
});
it('should set submitting indicators on submit', function() {
expect(scope.quickMessageButtonText).toBe('');
});
});
});
Your tests says that the mock http backend should receive a POST at the URL
apiUrl + 'google-form'
which, given the error message, is http://localhost:5000/google-form.
But your controller never sends a POST to that URL. It sends a POST to https://docs.google.com/forms/d/MyFormKey/formResponse. And it doesn't do it using angular's $http service, but does it behind its back, using jQuery.
As #JB Nizet pointed you are using jQuery instead of angular methods. In fact you should refactor your code a bit.
Its a great practice to keep things separate, like Controller from Service. In your case you are using service inside the controller. I would rather suggest you to create a service and then import that service in your controller. So basically here how the code will look like:
Controller
/* global $ */
'use strict';
angular.module('myApp')
.controller('QuickMessageCtrl', ['$scope', 'MyNewService', function ($scope, MyNewService) {
$scope.quickMessageButtonText = 'Send';
$scope.quickMessage = {
name: '',
email: '',
content: '',
};
function resetFormData() {
$('#name').val('');
$('#email').val('');
$('#content').val('');
}
$scope.postQuickMessageToGoogle = _.throttle(function() {
setSubmittingIndicators();
MyNewService.sendQuickMessage(
$scope.quickMessage.name,
$scope.quickMessage.email,
$scope.quickMessage.content
)
.success(
//sucess Message
//can be as well a function that returns a status code
)
.error(
//error Message
);
}, 500);
}]);
Service
'use strict';
angular.module('myApp')
.factory('MyNewService', ['$http', function ($http) {
var myService = {};
myService.sendQuickMessage = function(name, email, content) {
$http({
method: 'JSONP',
url: 'https://docs.google.com/forms/d/MyFormKey/formResponse?'+
'entry.3=' + name +
'&entry.1=' + email +
'&entry.0=' + content
});
};
return myService;
}]);
Unit-test
'use strict';
describe('Controller: QuickMessageCtrl', function() {
var $httpBackend, $rootScope, $controller, scope, apiUrl;
beforeEach(module('myApp'));
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
apiUrl = $injector.get('apiUrl');
$httpBackend.expectJSONP(
'https://docs.google.com/forms/d/MyFormKey/formResponse?'+
'entry.3=test'+
'&entry.1=test#test.com'+
'&entry.0=thisIsContent'
).respond(200, {});
$rootScope = $injector.get('$rootScope');
scope = $rootScope.$new();
$controller = $injector.get('$controller');
$controller('QuickMessageCtrl', { $scope: scope });
}));
describe('form submit', function() {
var changeStateSpy;
beforeEach(function() {
scope.quickMessage.name = 'test';
scope.quickMessage.content = 'thisIsContent';
scope.quickMessage.email ='test#test.com';
});
afterEach(function(){
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('should set submitting indicators on submit', function() {
scope.postQuickMessageToGoogle();
expect(scope.quickMessageButtonText).toBe('');
$httpBackend.flush();
});
});
});

AngularJS - Unit test for http get to JSON file

I am trying to write a unit test to test a simple factory that performs a http.get to retrieve a JSON file.
The factory is called within my controller.
Here's a plunker showing my http.get: http://plnkr.co/edit/xg9T5H1Kreo4lwxzRQem?p=preview
Ctrl:
app.controller('MainCtrl', function($scope, $http, factoryGetJSONFile) {
factoryGetJSONFile.getMyData(function(data) {
$scope.Addresses = data.Addresses.AddressList;
$scope.People = data.Names.People;
});
});
Factory:
app.factory('factoryGetJSONFile', function($http) {
return {
getMyData: function(done) {
$http.get('data.json')
.success(function(data) {
done(data);
})
.error(function(error) {
alert('An error occured whilst trying to retrieve your data');
});
}
}
});
Test:
// ---SPECS-------------------------
describe('with httpBackend', function () {
var app;
beforeEach(function () {
app = angular.mock.module('plunker')
});
describe('MyCtrl', function () {
var scope, ctrl, theService, httpMock;
beforeEach(inject(function ($controller, $rootScope, factoryGetJSONFile, $httpBackend) {
scope = $rootScope.$new(),
ctrl = $controller('MyCtrl', {
$scope: scope,
factoryGetJSONFile: theService,
$httpBackend: httpMock
});
}));
it("should make a GET call to data.json", function () {
console.log("********** SERVICE ***********");
httpMock.expectGET("data.json").respond("Response found!");
//expect(factoryGetJSONFile.getMyData()).toBeDefined();
httpMock.flush();
});
})
});
Error:
TypeError: 'undefined' is not an object (evaluating 'httpMock.expectGET')
You should assign $httpBackend to httpMock in beforeEach like this:
beforeEach(inject(function ($controller, $rootScope, factoryGetJSONFile, $httpBackend) {
httpMock = $httpBackend;
scope = $rootScope.$new();
ctrl = $controller('MyCtrl', {
$scope: scope,
factoryGetJSONFile: factoryGetJSONFile,
$httpBackend: httpMock
});
}));

Resources