Angular JS Unit Testing (Karma Jasmine) - angularjs

This is my service
angular.module('providers',)
.provider('sample', function(){
this.getName = function(){
return 'name';
};
this.$get = function($http, $log, $q, $localStorage, $sessionStorage) {
this.getTest = function(){
return 'test';
};
};
});
This is my unit test
describe('ProvideTest', function()
{
beforeEach(module("providers"));
beforeEach(function(){
module(function(sampleProvider){
sampleProviderObj=sampleProvider;
});
});
beforeEach(inject());
it('Should call Name', function()
{
expect(sampleProviderObj.getName()).toBe('name');
});
it('Should call test', function()
{
expect(sampleProviderObj.getTest()).toBe('test');
});
});
I am getting an error Type Error: 'undefined' is not a function evaluating sampleProviderObj.getTest()
I need a way to access function inside this.$get . Please help

You should inject your service into the test. Replace this:
beforeEach(function(){
module(function(sampleProvider){
sampleProviderObj=sampleProvider;
});
});
beforeEach(inject());
With this:
beforeEach(inject(function(_sampleProvider_) {
sampleProvider = _sampleProvider_;
}));

Firstly, you need, as had already been said, inject service, that you test. Like following
beforeEach(angular.mock.inject(function ($injector) {
sampleProviderObj = $injector.get('sample');
}));
Second, and more important thing. Sample have no any getTest functions. If you really need to test this function, you should as "Arrange" part of your test execute also $get function of your provider. And then test getTest function of result of previous execution. Like this:
it('Should call test', function()
{
var nestedObj = sampleProviderObj.$get(/*provide correct parameters for this function*/)
expect(nestedObj.getTest()).toBe('test');
});
But it's not good because this test can fail even if nestedObj.getTest work properly (in case when sampleProviderObj.$get works incorrect).
And one more thing, it seems like you need to inject this services $http, $log, $q, $localStorage, $sessionStorage to you provider rather then passing them as parameters.

Related

Unit test an angular controller and service which uses a promise?

I cannot get the test result to pass I'm using a very basic implementation to understand testing deeper.
I have a factory which returns a promise, accessed from my controller. I want to test that the call succeeds and assigns the response to the repos var. Following is the code:
'use strict';
angular.module('app')
.factory('searchServ', function ($timeout, $q, $http) {
return {
fetch: function(user) {
var deferred = $q.defer();
$timeout(function(){
$http({method: 'GET', url: 'https://api.github.com/users/' + user + '/repos'}).then(function(repos) {
deferred.resolve(repos.data);
}, function(reason){
deferred.reject(reason.status);
console.log(reason);
});
}, 30);
return deferred.promise;
}
};
})
.controller('MainCtrl', function ($scope, searchServ) {
$scope.results = function(user) {
$scope.message = '';
searchServ.fetch(user).then(function (repos) {
if(repos.length){
$scope.message = '';
$scope.repos = repos;
}
else{
$scope.message = 'not found'
}
}, function (){
$scope.message = 'not found';
});
};
});
//Test
'use strict';
describe('MainCtrl', function () {
var scope, searchServ, controller, deferred, repos = [{name: 'test'}];
// load the controller's module
beforeEach(module('app'));
beforeEach(inject(function($controller, $rootScope, $q) {
searchServ = {
fetch: function () {
deferred = $q.defer();
return deferred.promise;
}
};
spyOn(searchServ, 'fetch').andCallThrough();
scope = $rootScope.$new();
controller = $controller('MainCtrl', {
$scope: scope,
fetchGithub: fetchGithub
});
}));
it('should test', function () {
expect(scope.test).toEqual('ha');
});
it('should bind to scope', function () {
scope.results();
scope.$digest();
expect(scope.message).toEqual('');
//expect(scope.repos).not.toBe(undefined);
});
});
Running the test gives me the following error :
TypeError: undefined is not a function (evaluating 'spyOn(searchServ, 'fetch').andCallThrough()') in test/spec/controllers/main.js (line 15)
Any idea how I can test this such that it tests the scope binding as well as the async call?
There are a lot of issues with your code.
I've created this Plunkr for the purpose. index.js is the file with your code and test cases. I've edited most of the part according to the conventions and best-practices.
There are a few pointers I wanted to give you:
Since $http returns a promise, you should use that, instead of resolving the promise and creating another promise from your method. Not sure why is timeout used. So I removed $q and $timeout from searchServ's dependencies.
I did the same in the test case by removing the deferred variable that you used.
You should be using angular-mocks.js to mock your services and other dependencies instead of defining a service inside your test case(The way you have did.)
You should create separate describe blocks for testing different parts of your code(a controller in this case).
Hope this helps!

AngularJSUnitTesting : Expected spy function() to have been called

Below is my code , I dont want the service function to be invoked so I am using spy, but its giving error.I am not able to figure it out.
'use strict';
describe('Testing DetailCtrl\n\n\n', function() {
beforeEach(module("safe-repository"));
var $controller, $scope, controller;
var services = {
documentService:null
};
// Initialization before tests
beforeEach(inject(function(_$controller_, _documentService_){
$controller = _$controller_;
$scope = {};
controller = $controller('DetailCtrl', { $scope: $scope });
services.documentService=_documentService_;
spyOn(services.documentService, 'deleteDocument').and.callFake(function(){
console.log("inside delete function");
});
}));
describe('Testing self.deleteFile() function for different test cases\n\n', function() {
it(' When user has access permission to delete file/doc', function(done) {
expect(services.documentService.deleteDocument).toHaveBeenCalled();
// Inform jasmine that the test finish here
done();
});
});
});
Any help is appreciated.
you don't need ...
var services = {
documentService:null
};
that code is just confusing matters.
you should simplify this to ...
// services.documentService=_documentService_; // WHY DO THIS??
documentService=_documentService_;
then ...
spyOn(documentService, 'deleteDocument').and.callFake ... etc
then ...
expect(documentService.deleteDocument).toHaveBeenCalled();
you might also want to try ...
spyOn(loginService, 'isSuperAdmin').and.returnValue("something");
INSTEAD OF callFake (your expect statement would remain unchanged)
ALSO ...
I assume your controller is making the expected call to this method during construction? e.g the following line makes the expected call during construction?
controller = $controller('DetailCtrl', { $scope: $scope });
In other words your controller should look something like ...
app.controller("DetailCtrl", function($scope, documentService) {
// some other code
documentService.deleteDocument(); // MAKE SURE THIS CODE IS ACTUALLY BEING HIT IF ITS WRAPPED IN A CONDITIONAL STATEMENT
// some other code
});
Try this, it might help you :)
'use strict';
describe('Testing DetailCtrl\n\n\n', function() {
var $controller, scope, ctrl, mockService;
beforeEach(module("safe-repository"));
beforeEach(inject(function($rootScope, _$controller_){
scope = $rootScope.$new();
function del() {
//your return value
}
mockService = {
deleteDocument: del
}
$controller = _$controller_;
}));
function initController(){
ctrl = $controller('DetailCtrl', {
$scope: scope,
documentService: mockService
});
}
it(' When user has access permission to delete file/doc', function() {
spyOn(documentService,'deleteDocument').and.callThrough();
initController();
expect(mockService.deleteDocument).toHaveBeenCalled();
});
});
});

How to test .then(function in Unit Testing AgularJS with jasmine

I am not clear how to use SpyOn in Unit Testing...
I have the following controller
(function () {
'use strict';
angular.module('otpConfigureDatasets').controller('otpActivityCardController', otpActivityCardController);
otpActivityCardController.$inject = ['$location', '$state', 'otpWebMapApp', 'otpWMDeltaTracker', 'otpWMStateCache', '$scope', '$timeout', 'otpActivityCardService', 'otpControlCenterData'];
function otpActivityCardController($location, $state, otpWebMapApp, otpWMDeltaTracker, otpWMStateCache, $scope, $timeout, otpActivityCardService, otpControlCenterData) {
var vm = this;
vm.cards = [];
otpActivityCardService.getActivityCards().then(function (resolve) {
vm.cards = resolve;
});
//.....Some code ....
})();
I need to test the GetActivityCards().then(function ...
I tried test it using the code below
'use strict';
describe('Test controller (activityCard) in Page MyDatasets', function() {
var MainCtrl, $state, scope, otpWebMapApp, otpWMDeltaTracker, otpWMStateCache, otpActivityCardService, otpControlCenterData;
var card;
beforeEach(function() {
module('otpConfigureDatasets');
});
beforeEach(inject(function ($controller, $rootScope, _$state_, _otpWebMapApp_, _otpWMDeltaTracker_, _otpWMStateCache_, _otpActivityCardService_, _otpControlCenterData_) {
scope = $rootScope.$new();
scope.$parent = { $parent: { menuParentGroupClick: function menuParentGroupClick() { } } };
MainCtrl = $controller('otpActivityCardController', {
$scope: scope
});
otpWebMapApp = _otpWebMapApp_;
otpWMDeltaTracker = _otpWMDeltaTracker_;
otpWMStateCache = _otpWMStateCache_;
otpActivityCardService = _otpActivityCardService_;
otpControlCenterData = otpControlCenterData;
}));
it('Test Function', function() {
spyOn(otpActivityCardService, 'getActivityCards');
expect(otpActivityCardService.getActivityCards).toHaveBeenCalled();
});
});
But I am getting this error:
Expected spy getActivityCards to have been called.
Error: Expected spy getActivityCards to have been called.
What is wrong?
You created a spy to the "getActivityCards" function, but you didn't call it in your test (unless you hid this line of code from the example).
When you create a Jasmine Spy to a function, you are only "watching" this function, you can check if it was called, you can mock the return values of it, you can check the parameters of a call to it, i.e, you can check a lot of things about the call history of the function, but you still need to explicity make a call to the function (or to a function in your controller that calls the spied function from it).
So you are spying the Service, and you are testing the Controller, your test should look something like:
it('Test Function', function() {
spyOn(otpActivityCardService, 'getActivityCards');
otpActivityCardService.getActivityCards();
expect(otpActivityCardService.getActivityCards).toHaveBeenCalled();
});
On a side note, to be more testable, your controller should encapsulate your service call in a function in your controller, like:
(function () {
'use strict';
angular.module('otpConfigureDatasets').controller('otpActivityCardController', otpActivityCardController);
otpActivityCardController.$inject = ['$location', '$state', 'otpWebMapApp', 'otpWMDeltaTracker', 'otpWMStateCache', '$scope', '$timeout', 'otpActivityCardService', 'otpControlCenterData'];
function otpActivityCardController($location, $state, otpWebMapApp, otpWMDeltaTracker, otpWMStateCache, $scope, $timeout, otpActivityCardService, otpControlCenterData) {
var vm = this;
vm.cards = [];
vm.getCards = function () {
otpActivityCardService.getActivityCards().then(function (resolve) {
vm.cards = resolve;
});
}
vm.getCards();
//.....Some code ....
})();
So you could create a test that really tested a function in your controller (because the way you are describing your test case, it really should be a Service test only)
it('Better test case', function() {
spyOn(otpActivityCardService, 'getActivityCards');
MainCtrl.getCards();
expect(otpActivityCardService.getActivityCards).toHaveBeenCalled();
});

Testing angular controller initialisation with different conditions

I have a controller that takes a dependency on a service, and as part of it's initialisation calls a function on the service. Here's a contrived example:
describe('tests', function() {
var _scope, service, serviceValue = 'value';
beforeEach(module('app'));
beforeEach(inject(['$rootScope','$controller', function($rootScope, $controller) {
_scope = $rootScope.$new();
service = {
get: function(key) {
return serviceValue;
}
};
$controller('myController', {
'$scope': _scope,
'service': service
});
}]));
describe('initialisation', function() {
describe('key exists', function() {
it('should find the key', function() {
expect(_scope.message).toBe('found the key');
});
});
describe('key does not exist', function() {
beforeEach(function() {
serviceValue = undefined;
});
it('should not find the key', function() {
expect(_scope.message).toBe('did not find the key');
});
});
});
});
angular.module('app').controller('myController', ['$scope','service',
function($scope, service) {
if(service.get('key') === 'value') {
$scope.message = 'found the key';
} else {
$scope.message = 'did not find the key';
}
});
The tests for when the key does not exist fail because the controller initialisation has run in the first beforeEach, before the next beforeEach runs to change the service return value.
I can get around this by recreating the whole controller in the beforeEach of the 'key does not exist' tests, but this seems wrong to me, as it initialises the controller twice for the test. Is there a way to get the controller initialisation to run for every test, but after all other beforeEach functions have run.
Is this the right way to be initialising controllers? Am I missing some feature of jasmine?
Creating the controller for each test is the recommended way, especially when you have initialization logic.
I would however use Jasmine's spyOn to set up what the service returns and tracking calls to it, instead of modifying internal values of a mocked or real service.
Inject the real service and save it in a variable, and define a function that creates the controller:
describe('tests', function() {
var $scope, createController, service;
beforeEach(function() {
module('app');
inject(function($rootScope, $controller, _service_) {
$scope = $rootScope.$new();
service = _service_;
createController = function() {
$controller('myController', {
'$scope': $scope,
'service': service
});
};
});
});
For each test use spyOn to intercept calls to the service and decide what it should return, then create the controller:
describe('initialisation', function() {
it('should find the key', function() {
spyOn(service, 'get').and.returnValue('value');
createController();
expect($scope.message).toBe('found the key');
});
it('should not find the key', function() {
spyOn(service, 'get').and.returnValue(undefined);
createController();
expect($scope.message).toBe('did not find the key');
});
});
Demo: http://plnkr.co/edit/BMniTis1RbOR0h5O4kZi?p=preview
As spyOn sets up tracking you can now for example also make sure the service only gets called once on controller initilization:
spyOn(service, 'get').and.returnValue('value');
expect(service.get.calls.count()).toEqual(0);
createController();
expect(service.get.calls.count()).toEqual(1);
Note: The examples above use Jasmine 2.0. Syntaxes will have to be slightly modified for older versions.

Using Jasmine spyOn with $resource

I’m trying to use Jasmine spies for testing a controller that calls query on a $resource. I can get a successful test when I write my call to the resource as follows (implementation 1 in the plunk linked below)
function($scope, bagelApiService) {
bagelApiService
.query()
.$promise
.then(function(bagelsResponse) {
$scope.bagels = bagelsResponse;
$scope.somethingAfterBagelsLoad = true;
});
}
But I would rather call the resource like this (implementation 2 in the plunk linked below)
function($scope, bagelApiService) {
bagelApiService.query(function(bagelsResponse) {
$scope.bagels = bagelsResponse;
$scope.somethingAfterBagelsLoad = true;
});
}
Here is my spec
describe('BreakfastCtrl', function() {
var $q,
$rootScope,
_scope,
mockBagelsResponse = [{name: 'foobagel'}, {name: 'barbagel'}];
beforeEach(module('BreakfastApp'));
beforeEach(inject(function($controller, $q, $rootScope, bagelApiService) {
_scope = $rootScope.$new();
var queryDeferred = $q.defer();
spyOn(bagelApiService, 'query').andReturn({$promise: queryDeferred.promise});
$controller('BreakfastCtrl', {
'$scope': _scope,
'bagelApiService': bagelApiService
});
queryDeferred.resolve(mockBagelsResponse);
$rootScope.$apply();
}));
it('should set scope.bagels', function() {
expect(_scope.bagels).toEqual(mockBagelsResponse);
});
});
Any idea why implementation 2 fails the test (even though it runs fine), and how the test can be written to pass with implementation 2?
click here for plunk
It's because you don't mimic the query() completely.
It should be like this (at least to make both of your implementations work).
spyOn(bagelApiService, 'query').andCallFake(function (callback) {
queryDeferred.promise.then(callback);
return { $promise: queryDeferred.promise };
});
Example Plunker: http://plnkr.co/edit/wGAytf5ASSJwut4WUwGO?p=preview

Resources