Testing angular controller initialisation with different conditions - angularjs

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.

Related

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
});
});
});

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();
});
});
});

Angular JS Unit Testing (Karma Jasmine)

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.

How to test saving a resource in a controller with a promise

I have a controller that saves a resource. I can't tell how to "access" the part of the code that executes after the promise resolves. What do I need to change about my test or controller in order to get it to work? Here's the code.
Controller:
'use strict';
/**
* #ngdoc function
* #name lunchHubApp.controller:AnnouncementsCtrl
* #description
* # AnnouncementsCtrl
* Controller of the lunchHubApp
*/
angular.module('lunchHubApp')
.controller('AnnouncementsCtrl', ['$scope', 'Announcement', function ($scope, Announcement) {
$scope.announcements = [];
$scope.save = function() {
// This next line is the part I'm finding hard to test.
new Announcement($scope.announcement).create().then(function(announcement) {
$scope.foo = 'bar'
});
};
}]);
Test:
'use strict';
describe('AnnouncementsCtrl', function() {
beforeEach(function() {
module('lunchHubApp', 'ng-token-auth')
});
it('sets scope.announcements to an empty array', inject(function($controller, $rootScope) {
var scope = $rootScope.$new(),
ctrl = $controller('AnnouncementsCtrl', { $scope: scope });
expect(scope.announcements).toEqual([]);
}));
describe('save', function() {
it('works', inject(function($controller, $rootScope, _$httpBackend_) {
var $httpBackend = _$httpBackend_;
var scope = $rootScope.$new(),
ctrl = $controller('AnnouncementsCtrl', { $scope: scope });
expect(scope.announcements.length).toBe(0);
var announcement = {
restaurantName: 'Bangkok Taste',
userId: 1
};
scope.announcement = announcement;
$httpBackend.expect('POST', '/api/announcements').respond(200, announcement);
scope.save();
scope.$digest();
expect(scope.foo).toEqual('bar');
}));
});
});
Update: here's the way I ended up modifying my controller test. The following passes and has been refactored from the original.
'use strict';
describe('AnnouncementsCtrl', function() {
var $httpBackend,
announcement,
scope,
ctrl;
beforeEach(function() {
module('lunchHubApp');
inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
scope = $injector.get('$rootScope').$new();
ctrl = $injector.get('$controller')('AnnouncementsCtrl', { $scope: scope });
announcement = { restaurantName: 'Bangkok Taste' };
scope.announcement = { restaurantName: 'Jason\'s Pizza' };
$httpBackend.expect('GET', '/api/announcements').respond([announcement]);
});
});
it('sets scope.announcements to an empty array', function() {
expect(scope.announcements).toEqual([]);
});
it('grabs a list of announcements', function() {
expect(scope.announcements.length).toBe(0);
$httpBackend.flush();
expect(scope.announcements.length).toBe(1);
});
describe('save', function() {
beforeEach(function() {
$httpBackend.expect('POST', '/api/announcements').respond(200, { restaurantName: 'Foo' });
scope.save();
$httpBackend.flush();
});
it('adds an announcement', function() {
expect(scope.announcements.length).toBe(2);
});
it('clears the restaurant name', function() {
expect(scope.announcement.restaurantName).toEqual('');
});
});
});
I think what you're doing is good. Since the Angular resources are factories using the $http service in a restful way, you should use the expect of the $httpBackend just as you did.
One thing that you miss however is that you need to make sure your promise is resolved. But write async tests can be tricky in some cases. To do so, you have to use the flush() method of $httpBackend to force your test to be synchronous.
After the flush, you can make your expect normally. Also you might have to move your expectPOST before your $rootScope.$new() statement.
You can go with a change like this, I don't think the $digest() is necessary:
$httpBackend.expect('POST', '/api/announcements').respond(200, announcement);
scope.save();
$httpBackend.flush();
expect(scope.foo).toEqual('bar');
The tests you've started writing seem to be testing not just AnnouncementsCtrl, but the Announcements service/factory as well. The signs of this in this case are
You're not mocking the Announcements service/factory / not stubbing any of its methods.
There is no code in the AnnouncementsCtrl regarding making http requests, and yet you're using $httpBackend.expect(... in the tests for them.
The success/failure of the tests that claim to test AnnouncementsCtrl will succeed or fail depending on code in the Announcements service/factory.
This goes against what unit tests are usually used for: testing each component in isolation. Keeping the focus of this answer on testing the success callback passed to the then method of the promise returned by create, my suggestion is to mock the Announcements service/factory, so its create method returns a promise that you can control in the test. This mock would be of the form:
var MockAnnouncement = null;
var deferred = null;
beforeEach(module(function($provide) {
MockAnnouncement = function MockAnnouncement() {
this.create = function() {
return deferred.promise;
};
};
$provide.value('Announcement', MockAnnouncement);
}));
You would then have to make sure that you create deferred object before each test:
beforeEach(inject(function($rootScope, $controller, $q) {
$scope = $rootScope.$new();
deferred = $q.defer(); // Used in MockAnnouncement
ctrl = $controller('AnnouncementsCtrl', {
$scope: $scope
});
}));
This deferred object is then resolved in the test:
it('calls create and on success sets $scope.foo="bar"', function() {
$scope.save();
deferred.resolve();
$scope.$apply();
expect($scope.foo).toBe('bar');
});
A slightly extended version of this, testing a few other behaviours of the controller as well, can be seen at http://plnkr.co/edit/v1bCfmSPmmjBoq3pfDsk

Jasmine unit test asynchronous controller method

I'm using Jasmine to unit test an Angular controller which has a method that runs asynchronously. I was able to successfully inject dependencies into the controller but I had to change up my approach to deal with the async because my test would run before the data was loaded. I'm currently trying to spy on the mock dependency and use andCallThrough() but it's causing the error TypeError: undefined is not a function.
Here's my controller...
myApp.controller('myController', function($scope, users) {
$scope.user = {};
users.current.get().then(function(user) {
$scope.user = user;
});
});
and my test.js...
describe('myController', function () {
var scope, createController, mockUsers, deferred;
beforeEach(module("myApp"));
beforeEach(inject(function ($rootScope, $controller, $q) {
mockUsers = {
current: {
get: function () {
deferred = $q.defer();
return deferred.promise;
}
}
};
spyOn(mockUsers.current, 'get').andCallThrough();
scope = $rootScope.$new();
createController = function () {
return $controller('myController', {
$scope: scope,
users: mockUsers
});
};
}));
it('should work', function () {
var ctrl = createController();
deferred.resolve('me');
scope.$digest();
expect(mockUsers.current.get).toHaveBeenCalled();
expect(scope.user).toBe('me');
});
});
If there is a better approach to this type of testing please let me know, thank you.
Try
spyOn(mockUsers.current, 'get').and.callThrough();
Depends on the version you have used: on newer versions andCallThroungh() is inside the object and.
Here the documentation http://jasmine.github.io/2.0/introduction.html

Resources