How to spy controller method with jasmine? - angularjs

I have a simple controller with one method.
I set one watcher in the controller and I want to be sure that watcher listener invoked when a model is updated.
Do you know how to check whether watch listener is called?
var app = angular.module('app', []);
app.controller('MainController', ['$scope', function($scope){
$scope.test = 0;
this.method = function(){
console.log('called');
$scope.test = $scope.name.length;
};
$scope.$watch('name', this.method);
}]);
describe('controller method ', function(){
var scope;
var controller;
beforeEach(module('app'));
beforeEach(inject(function($injector){
scope = $injector.get('$rootScope').$new();
var $controller = $injector.get('$controller');
scope.name = 'blabla';
controller = $controller('MainController', { $scope: scope });
}));
it('should watch', function(){
var s = spyOn(controller, 'method');
scope.$digest();
expect(s).toHaveBeenCalled();
});
it('true should be true', function(){
expect(1).toBe(1);
});
});
http://plnkr.co/edit/B7pgd9x3bPtOlY40NkRm?p=preview

You donĀ“t want to spy on the controllerĀ“s method. You might want to rename that method, split it into multiple methods or refactor them in any other way in the future. this will break your test, while the actual behaviour of your controller might not have changed. so focus on testing the controllers behaviour, instead of spying on private methods.
it('should set the testing variable', function(){
// act
scope.name = 'name';
scope.$digest();
// assert
scope.testing.should.eql(4);
});

Related

Jasmine testing for function call

I'm new to jasmine testing. How can I test function call in watch function?
Below is my code. I'm confused about usage of spy in jasmine and how can I handle function call inside watcher.
Do I need to pause fetch() inside watch. Please suggest how to improve my testing skills.
var app = angular.module('instantsearch',[]);
app.controller('instantSearchCtrl',function($scope,$http,$sce){
$scope.$sce=$sce;
$scope.$watch('search', function() {
fetch();
});
$scope.search = "How create an array";
var result = {};
function fetch() {
$http.get("https://api.stackexchange.com/2.2/search?page=1&pagesize=10&order=desc&sort=activity&intitle="+$scope.search+"&site=stackoverflow&filter=!4*Zo7ZC5C2H6BJxWq&key=DIoPmtUvEkXKjWdZB*d1nw((")
.then(function(response) {
$scope.items = response.data.items;
$scope.answers={};
angular.forEach($scope.items, function(value, key) {
var ques = value.question_id;
$http.get("https://api.stackexchange.com/2.2/questions/"+value.question_id+"/answers?page=1&pagesize=10&order=desc&sort=activity&intitle="+$scope.search+"&site=stackoverflow&filter=!9YdnSMKKT&key=DIoPmtUvEkXKjWdZB*d1nw((").then(function(response2) {
$scope.answers[ques]=response2.data.items;
//console.log(JSON.stringify($scope.answers));
});
});
});
}
});
my test case:
describe('instantSearchCtrl', function() {
beforeEach(module('instantsearch'));
var $scope, ctrl;
beforeEach( inject(function($rootScope, $controller) {
// create a scope object for us to use.
$scope = $rootScope.$new();
ctrl = $controller('instantSearchCtrl', {
$scope: $scope
});
}));
/*var $scope = {};
var controller = $controller('instantSearchCtrl', { $scope: $scope });
expect($scope.search).toEqual('How create an array');
//expect($scope.strength).toEqual('strong');*/
it('should update baz when bar is changed', function (){
//$apply the change to trigger the $watch.
$scope.$apply();
//fetch().toHaveBeenCalled();
fetch();
it(" http ", function(){
//scope = $rootScope.$new();
var httpBackend;
httpBackend = $httpBackend;
httpBackend.when("GET", "https://api.stackexchange.com/2.2/search?page=1&pagesize=10&order=desc&sort=activity&intitle="+$scope.search+"&site=stackoverflow&filter=!4*Zo7ZC5C2H6BJxWq&key=DIoPmtUvEkXKjWdZB*d1nw((").respond([{}, {}, {}]);
});
});
});
First you should trigger the watch. For that you should change search value and after that manually run: $scope.$digest() or $scope.$apply()
In order to fully test the fetch function you should also mock the response to the second request (or the mock for all the second level requests if you want to test the iteration).
After that you should add some expect statements. For the controller code they should be:
expect($scope.items).toEqual(mockResponseToFirstRequest);
expect($scope.answers).toEqual(mockResponseCombinedForSecondLevelRequests);
As for using the spy in karma-jasmine tests, those limit the amount of the code tested. A plausible use for spy in this case is to replace the httpBackend.when with spyOn($http, 'get').and.callFake(function () {})
Here is the documentation for using spies https://jasmine.github.io/2.0/introduction.html#section-Spies

AngularJS- How to test that a funtion in controller is defined using jasmine

I have following controller. During unit testing I want to first test that all the controller properties and functions are defined before unit testing the individual logic.
angular.module('sampleModule')
.controller('SampleController', SampleController);
SampleController.$inject =['sampleService'];
function SampleController(sampleService){
this.property1 = 'some data';
this.property2 = 'some other data';
this.getData = function(){
//do something
}
this.postAttributes = function() {
sampleService.updateMethod(number,attributes)
.then(function(response){
//do something on successful update
},function(response){
//do something on unsuccessful update
});
};
}
Here is the sample spec that I'm using. I'm able to verify that the controller properties are defined after creating the SampleController instance using $controller service.
However when I perform the same assertion on functions, I get error saying function is undefined
describe('SampleController Test', function(){
var $controller;
var service;
beforeEach(angular.mock.module('sampleModule'));
beforeEach(angular.mock.inject(function(_$controller_){
$controller = _$controller_;
}));
it('Testing $scope variable', function(){
var sampleController = $controller('SampleController', {
sampleService: service, //mocked factory service
});
expect(sampleController.property1).toBeDefined();
expect(sampleController.property2).toBeDefined();
expect(sampleController.getData).toBeDefined(); //this assetion fails
});
});
Third assetion fails with below error:
Expected undefined to be defined.
What am I missing?! And is it a right approach to test all the controller properties and functions are defined before testing any individual logic?
Please do this changes.
describe("\n\nuser registration form testing", function () {
describe("SampleController Test", function () {
var scope;
beforeEach(angular.mock.module('sampleModule'));
beforeEach(inject(function ($rootScope,_$controller_) {
$controller = _$controller_;
scope = $rootScope.$new();
}))
it('Testing $scope variable', function(){
var sampleController = $controller('SampleController',{$scope: scope});
expect(sampleController.property1).toBeDefined();
expect(sampleController.property2).toBeDefined();
expect(sampleController.getData).toBeDefined(); //this assetion fails
});
});
});

AngularJs unit-test for timeout

I have a angular directive with the following snippet of code, how would I unit test this?
link: function($scope, iElm) {
$(iElm).on('paste', function(){
$timeout(function(){
$scope.lastScan = Date.now();
});
});
}
You need to use $timeout.flush() which will instantly call any $timeout callbacks you have defined, so it essentially makes it into synchronous code for testing purposes.
Try this:
DEMO
app.js
app.directive('myDirective', function($timeout){
return function($scope, iElm) {
// no need to use jQuery
iElm.bind('paste', function(){
console.log('**paste**')
$timeout(function(){
console.log('timeout')
$scope.lastScan = Date.now();
});
});
// $(iElm).on('paste', function(){
// $timeout(function(){
// $scope.lastScan = Date.now();
// });
// });
}
});
appSpec.js
describe("My directive", function() {
var element,
$timeout, $compile, $scope;
// load the module that has your directive
beforeEach(module('plunker'));
beforeEach(inject(function(_$timeout_, _$compile_, _$rootScope_) {
$timeout = _$timeout_;
$compile = _$compile_;
$scope = _$rootScope_.$new();
}));
function compileElement(){
// use $compile service to create an instance of the directive
// that we can test against
element = $compile('<my-directive></my-directive>')($scope);
// manually update scope
$scope.$digest();
}
it("defines a 'lastScan' property", function() {
compileElement();
// trigger paste event
element.triggerHandler('paste');
// angular doesn't know about paste event so need
// to manually update scope
$scope.$digest();
// immediately call $timeout callback
// thus removing any asynchronous awkwardness
$timeout.flush();
// assert that directive has added property to the scope
expect($scope.lastScan).toBeDefined();
});
});

How to test services in an AngularJS Controller?

My controller is:
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 test is pretty simple:
(function() {
describe('SourceController', function() {
var $scope, $rootScope, $httpBackend, createController;
beforeEach(module('angularMoon'));
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
$rootScope = $injector.get('$rootScope');
$scope = $rootScope.$new();
var $controller = $injector.get('$controller');
createController = function() {
return $controller('SourceController', {
'$scope': $scope
});
};
}));
it("should set the current menu item to 'source'", function() {
createController();
$scope.init();
expect($rootScope.currentItem).toBe('source');
});
it("should get the contents of the root folder", function() {
createController();
$scope.init();
// NOT SURE WHAT TO DO HERE!
});
});
})();
I want to test that the fileService had it's getContents function called and mock a response so that I can test the two scenarios (if is array and if isn't`)
I would recommend using Jasmine spies for this.
Here is an example that might help. I usually put the spyOn call in the beforeEach.
var mockedResponse = {};
spyOn(fileService, "getContents").andReturn(mockedResponse);
In the 'it' part:
expect(fileService.getContents).toHaveBeenCalled();
To get the response, just call the method in your controller that calls the fileService method. You may need to manually run a digest cycle too. Snippet from one of my tests:
var testOrgs = [];
beforeEach(inject(function(coresvc) {
deferred.resolve(testOrgs);
spyOn(coresvc, 'getOrganizations').andReturn(deferred.promise);
scope.getAllOrganizations();
scope.$digest();
}));
it("getOrganizations() test the spy call", inject(function(coresvc) {
expect(coresvc.getOrganizations).toHaveBeenCalled();
}));
it("$scope.organizations should be populated", function() {
expect(scope.allOrganizations).toEqual(testOrgs);
expect(scope.allOrganizations.length).toEqual(0);
});
deferred in this case is a promise created with $q.defer();
You can create a spy and verify only that fileService.getContents is called, or either verify extra calls (like promise resolution) by making the spy call through. Probably you should also interact with httpBackend since you may need to flush the http service (even though you use the mock service).
(function() {
describe('SourceController', function() {
var $scope, $rootScope, $httpBackend, createController, fileService;
beforeEach(module('angularMoon'));
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
$rootScope = $injector.get('$rootScope');
$scope = $rootScope.$new();
// See here
fileService = $injector.get('fileService');
spyOn(fileService, 'getContents').andCallThrough();
var $controller = $injector.get('$controller');
createController = function() {
return $controller('SourceController', {
'$scope': $scope
'fileService': fileService
});
};
}));
it("should get the contents of the root folder", function() {
createController();
$scope.init();
expect(fileService.getContents).toHaveBeenCalled();
});
});
})();
You can also add expectations to what happens inside the callback but you should issue a httpBackend.flush() before.

Testing output from event listener in Angular service

This is a problem that's been stuming me for a few days. Plunkr with code sample is here: http://plnkr.co/edit/QHJCyKfM2yFyCQzB68GS?p=preview
Basically, I have a service factory that is listening for an event and returning a value.
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.$on("myEvent", function(args, value) {
$scope.myEventCalled = true;
});
});
app.factory('myService', function($rootScope){
var mysvc = {}
mysvc.testVar = true
$rootScope.$on('otherEvent', function(args, value){
$rootScope.rootCalled = true;
mysvc.testVar = false;
});
return mysvc
});
My test is:
describe('Testing a Hello World controller', function() {
var $scope = null;
var svc = null
//you need to indicate your module in a test
beforeEach(module('plunker'));
it('should be able to see settings in service', inject(function($rootScope, $controller) {
$scope = $rootScope.$new();
inject(function($httpBackend, myService) {
svc = myService
})
expect(svc).toBeDefined();
expect(svc.testVar).toBe(true);
}));
it('should invoke otherEvent when "otherEvent" broadcasted in service', inject(function($rootScope, $controller) {
$scope = $rootScope.$new();
inject(function($httpBackend, myService) {
svc = myService
})
$scope.$broadcast('otherEvent');
expect(svc.testVar).toBe(false);
}));
});
The first test passes but the second test fails, as it's not able to trigger the event and access the modified value.
I get how to test this with a controller, but verifying that the value has been changed from a service has escaped me. I would be grateful for any suggestions.
myService is listening on $rootScope, but in your second test the event is broadcasted on $scope. Since $broadcast dispatches an event name downwards to all child scopes and their children, it will not reach $rootScope.
Change it to this in the second test and it will work:
$rootScope.$broadcast('otherEvent');
However, when testing a controller it's recommended to use mocked versions of its dependencies and only test the controllers own functionality in isolation. In the same fashion each service should get its own spec.

Resources