AngularJS Testing with Jasmine and Karma - javascript

i try to test an angular controller but he returns a promise and i cant really resolve it...what's wrong with my code?
the Angular Controller:
kpi.AboutController = function ($scope, Version) {
function loadVersion() {
//$scope.version = Version.getVersion();
$scope.version = '1.1.0';
}
loadVersion();
};
and the jasmine test:
describe('dashboard: AboutController', function() {
beforeEach(module('dashboard'));
var $controller;
beforeEach(inject(function(_$controller_){
// The injector unwraps the underscores (_) from around the parameter names when matching
$controller = _$controller_;
}));
it('testing the version number', function() {
var $scope = {};
var controller = $controller('AboutController', { $scope: $scope });
var version = 0;
console.log($scope.version);
expect($scope.version).toContain("1.");
});
});
this is working, but when i change the line $scope.version = '1.1.0'; to $scope.version = Version.getVersion(); i receive a promise and i can not rly check it....i tried to resolve it with "then()" function via $scope.version.then(...)but that did not work...what to do?
edit:
the following error occures:
Expected e({ $promise: Object({ $$state: Object({ status: 0 }) }), $resolved: false }) to contain '1.'.
at Object.<anonymous>
and the Service:
kpi.VersionFactory = function ($resource, appConfig) {
var Version = $resource(thePath, {}, {
"getVersion": {
method: "GET",
url: thePath,
isArray: false
}
});
return Version;
};

you need to pass a callback into your test case .
kpi.AboutKPIController = function ($scope, Version) {
$scope.loadVersion= function () {
Version.getVersion().then(function(res){
$scope.version = res.data;
})
}
$scope.loadVersion();
};
describe('dashboard: AboutKPIController', function() {
beforeEach(module('dashboard'));
var $controller;
beforeEach(inject(function(_$controller_){
// The injector unwraps the underscores (_) from around the parameter names when matching
$controller = _$controller_;
}));
it('testing the version number', function(done) {
var $scope = {};
var controller = $controller('AboutKPIController', { $scope: $scope });
var version = 0;
console.log($scope.version);
$scope.loadVersion().then(function(res)){
//can test here
expect($scope.version).toContain("1.");
done();
});
});
});

I expect Version.getVersion(); returns a promise. Your controller implemnentation should look like
function loadVersion() {
Version.getVersion().then(function(version) {
$scope.version = version;
});
}
In your test code use $scope.$apply to resolve promises before you perform except.
beforeEach(inject(function(_$controller_, _$rootScope_){
$controller = _$controller_;
$rootScope = _$rootScope_;
}));
it('testing the version number', function() {
var $scope = $rootScope.$new();
var controller = $controller('AboutKPIController', { $scope: $scope });
controller.loadVersion();
$scope.$apply();
expect($scope.version).toContain("1.");
});

Related

AngularJS 1.x Component testing with Karma resolve promise to test success function (spyOn)

I'm lost. I try to test if state.go is called after a ApiServiceMock has resolved a promise after a fake login.
For this test I get:
Expected spy go to have been called.
If I test another function which is directly triggering state.go it works. So I guess the promise isn't being resolved in the first place.
Using Angular Components and testing is quite new to me. Would be great if someone could give me a hint what I'm doing wrong or let me know if the whole approach is wrong in the first place.
login.component.ctrl.js
module.exports = function LoginComponentCtrl($state, $log, apiService) {
var vm = this;
vm.submit = function() {
apiService
.login(vm.username, vm.password)
.then(function(response) {
$state.go('storeDetail');
})
.catch(function(errData) {
$log.error(errData);
});
};
};
login.spec.js
var app = require('../../app.js');
var login = require('./login.module.js');
describe('login', function() {
var controller;
var element;
var scope;
var state;
var ApiServiceMock;
var StateMock;
var deferred;
beforeEach(angular.mock.module(app));
beforeEach(angular.mock.module(login));
describe('Component: login', function () {
beforeEach(inject(function($rootScope, $compile, $componentController, $q){
deferred = $q.defer();
ApiServiceMock = {
login: function () {
return deferred.promise;
}
};
StateMock = {
go: function () {
return true;
}
};
scope = $rootScope.$new();
controller = $componentController('login', {
$scope: scope,
apiService: ApiServiceMock,
$state: StateMock
});
element = angular.element('<login></login>');
element = $compile(element)(scope);
scope.$apply();
}));
it('on successful login', function() {
controller.username = 'Michael Jackson';
controller.password = 'dangerous123';
spyOn(StateMock, 'go').and.callThrough();
controller.submit();
deferred.resolve();
scope.$digest();
expect(StateMock.go).toHaveBeenCalled();
}););
});
});
Let me know if I can add further information to make this clearer.
Just directly return resolved promise from your mock service.
Try this
beforeEach(inject(function($rootScope, $compile, $componentController, $q){
ApiServiceMock = {
login: function () {
return $q.resolve({}); // if angular version is 1.4+
//return $q.when({}); // if angular less than 1.4
}
};
StateMock = {
go: function () {
return true;
}
};
scope = $rootScope.$new();
controller = $componentController('login', {
$scope: scope,
apiService: ApiServiceMock,
$state: StateMock
});
element = angular.element('<login></login>');
element = $compile(element)(scope);
scope.$apply();
}));
it('on successful login', function() {
controller.username = 'Michael Jackson';
controller.password = 'dangerous123';
spyOn(StateMock, 'go').and.callThrough();
controller.submit();
scope.$digest();
expect(StateMock.go).toHaveBeenCalled();
});
);

Mock factory on controller test

this is my controller:
angular
.module('studentsApp')
.controller('StudentsController', StudentsController);
function StudentsController($scope, StudentsFactory) {
$scope.students = [];
$scope.specificStudent= {};
var getStudents = function() {
StudentsFactory.getStudents().then(function(response) {
if($scope.students.length > 0){
$scope.students = [];
}
$scope.students.push(response.data);
});
};
}
This is my factory:
angular.module('studentsApp')
.factory('StudentsFactory', function($http) {
var base_url = 'http://localhost:3000';
var studentsURI = '/students';
var studentURI = '/student';
var config = {
headers: {
'Content-Type': 'application/json'
}
};
return {
getStudents: function() {
return $http.get(base_url + studentsURI);
}
};
});
And here is how I'm trying to unit test the controller:
describe('Controller: Students', function() {
var StudentsController, scope, StudentsFactory;
beforeEach(function() {
module('studentsApp');
inject(function($rootScope, $controller, $httpBackend, $injector) {
scope = $rootScope.$new();
httpBackend = $injector.get('$httpBackend');
StudentsFactory = $injector.get('StudentsFactory');
StudentsController = $controller('StudentsController', {
$scope : scope,
'StudentsFactory' : StudentsFactory
});
students = [{
name: 'Pedro',
age: 10
}, {
name: 'João',
age: 11
}, {
name: 'Thiago',
age: 9
}];
spyOn(StudentsFactory, 'getStudents').and.returnValue(students);
});
});
it('Should get all students', function() {
scope.students = [];
StudentsController.getStudents();
$scope.$apply();
expect(scope.students.length).toBe(3);
});
});
The problem is when I run the test, the following message is displayed:
undefined is not a constructor (evaluating
'StudentsController.getStudents()')
I looked at the whole internet trying to find a tutorial that can help me on that, but I didn't find anything, could someone help me here?
It's link to the fact that the function getStudent() is private (declared by var). Thus your test can't access it. You have to attach it to the $scope or this to be able to test it.
I generally use this in controller:
var $this = this;
$this.getStudents = function() {
...
};
There's no StudentsController.getStudents method. It should be
this.getStudents = function () { ... };
Mocked StudentsFactory.getStudents returns a plain object, while it is expected to return a promise.
$controller shouldn't be provided with real StudentsFactory service as local dependency (it is already provided with it by default):
var mockedStudentsFactory = {
getStudents: jasmine.createSpy().and.returnValue($q.resolve(students))
};
StudentsController = $controller('StudentsController', {
$scope : scope,
StudentsFactory : mockedStudentsFactory
});

Jasmine Mock for Angular service using promise

I have the following Angular service and mock/test setup. I cannot get the mock http request to work properly. I'm returning a promise from my factory and have the HTTP request mocked but the data in the controller's scope is not the data returned from the http service that the factory calls.
As you can see, I have called $digest() on $rootScope and $httpBackend. I'm at a loss.
You can see from the fiddle that the code works properly and displays the text from the service, but the mock is not working.
Why?
The fiddle is here: https://jsfiddle.net/mbaranski/gnxxbgy3/
Here is the code:
var myApp = angular.module('myApp', []);
var ep = '/echo/json/';
myApp.factory('dataFactory', function($http) {
var getData = function() {
var data = $.param({
json: JSON.stringify({
name: "Name from Echo Service"
})
});
return $http.post(ep, data);
}
return {
getData: getData
}
});
myApp.controller('MyCtrl', function($scope, dataFactory) {
$scope.name = 'Original Name';
dataFactory.getData().then(function(data) {
$scope.name = data.data.name;
});
});
describe('Test For Site', function() {
beforeEach(angular.mock.module('myApp'));
var $controller;
var $httpBackend;
var $q;
var $rootScope;
beforeEach(angular.mock.inject(function(_$controller_, _$httpBackend_, _$q_, _$rootScope_) {
$controller = _$controller_;
$httpBackend = _$httpBackend_;
$q = _$q_;
$rootScope = _$rootScope_;
}));
describe('test pageController', function() {
it('Should pass', function() {
var scope = {};
$httpBackend.expect('POST', ep);
$httpBackend.whenPOST(ep, function(str) {
return true;
}).respond(
function() {
return {
name: "Name from Echo Service"
};
});
var controller = $controller('MyCtrl', {
$scope: scope,
});
$httpBackend.flush();
$rootScope.$digest();
$rootScope.$flush();
expect(scope.name).toBe('Name from Echo Service');
});
});
});
Here's the HTML
<div ng-controller="MyCtrl">
Hello, {{name}}!
</div>
<br/>
I was really minute problem, which took a lot to find it. .whenPOST of $httpBackend should only return object/string & your were returning a function which was returning by from .respond of ..whenPOST method.
Just by returning plain JSON, rather than returning function solved the issue.
Code
$httpBackend.whenPOST(ep, function(str) {
return true;
}).respond({
name: "Name from Echo Service"
});
Also remove $rootScope.$flush(); this line. $rootScope doesn't have $flush method in it.
Forked JSFiddle

Karma-Jasmine: How to test ionicModal?

THE SITUATION:
In my Ionic app I am testing the correct opening of a modal.
I have made several attempts, but i am getting the following error:
TypeError: Cannot read property 'then' of undefined
THE FUNCTION:
$scope.open_register_modal = function()
{
$ionicModal.fromTemplateUrl('templates/project_register.html', {
scope: $scope
}).then(function(modal) {
$scope.modal_register = modal;
$scope.modal_register.show();
});
};
THE TEST:
describe('App tests', function() {
beforeEach(module('my_app.controllers'));
beforeEach(inject(function(_$controller_, _$rootScope_)
{
$controller = _$controller_;
$rootScope = _$rootScope_;
$scope = _$rootScope_.$new();
$ionicModal =
{
fromTemplateUrl: jasmine.createSpy('$ionicModal.fromTemplateUrl'),
then : function(modal){} // <--- attempt
};
var controller = $controller('MainCtrl', { $scope: $scope, $rootScope: $rootScope, $ionicModal: $ionicModal });
}));
describe('Modal tests', function()
{
it('should open register modal', function()
{
$scope.open_register_modal();
expect($ionicModal).toHaveBeenCalled();
});
});
});
ATTEMPTS:
These are some of the attempts to initialize $ionicModal:
1.
$ionicModal =
{
fromTemplateUrl: jasmine.createSpy('$ionicModal.fromTemplateUrl'),
then : function(modal){}
};
2.
$ionicModal =
{
fromTemplateUrl: jasmine.createSpy('$ionicModal.fromTemplateUrl'),
then: jasmine.createSpy('$ionicModal.then')
};
3.
$ionicModal =
{
fromTemplateUrl: jasmine.createSpy('$ionicModal.fromTemplateUrl'),
then: jasmine.createSpy('$ionicModal.fromTemplateUrl.then')
};
4.
$ionicModal = jasmine.createSpyObj('$ionicModal', ['show', 'close','fromTemplateUrl']);
But they all give the same error:
TypeError: Cannot read property 'then' of undefined
THE QUESTION:
How can i pass the .then method inside the test?
How can i properly test ionicModal?
I don't know anything about ionic, but I think your mistake is expecting that the method then is part of it. The code
$ionicModal.fromTemplateUrl('templates/project_register.html', {
scope: $scope
}).then(function(modal) {
$scope.modal_register = modal;
$scope.modal_register.show();
});
can be refactor to:
var temp=$ionicModal.fromTemplateUrl(
'templates/project_register.html',
{scope: $scope});
temp.then(function(modal) {
$scope.modal_register = modal;
$scope.modal_register.show();
});
so the method then is part of the object returned by the call to fromTemplateUrl
A solution could be something like:
function fakeTemplate() {
return { then:function(){}}
}
$ionicModal = {
fromTemplateUrl: jasmine.createSpy('$ionicModal.fromTemplateUrl').and.callFake(fakeTemplate)
};

Testing promises in angular controllers with Jasime

I am having a bit of trouble getting set up to test an Angular controller that contains promises.
The controller code it this:
angular.module('jhApp')
.controller('adminPagesCtrl', function(resourceCache) {
var adminPages = this;
adminPages.items = resourceCache.query('page');
adminPages.delete = function(page) {
resourceCache.delete('page', {id:page._id})
.then(function(responceData) {
if(responceData.deleted === true) {
adminPages.items = resourceCache.query('page');
}
});
};
});
my test looks like this:
describe('adminPagesCtrl', function() {
var defferred,
$rootScope,
controller,
resourceCache,
scope,
page,
defferred,
promise;
beforeEach(function() {
module('jhApp');
});
beforeEach(inject(function ($rootScope, $controller, $q) {
scope = $rootScope.$new();
controller = $controller('adminPagesCtrl as adminPages', {$scope: scope});
deffered = $q.defer();
promise = deffered.promise;
resourceCache = {
delete: promise
};
page = {_id: 1};
spyOn(resourceCache, 'delete');
}));
it('deletes a page', function() {
expect(controller).toBeDefined();
scope.adminPages.delete(page);
console.log(resourceCache.delete) //outputs: function{}
console.log($rootScope) //outputs: undefined
resourceCache.delete.resolve({deleted: true});
$rootScope.$apply();
expect(resourceCache.delete).toHaveBeenCalled();
});
});
I am trying to mock the resourceCache promise so it returns some fake data and so I can just test that something got returned and the adminPages.delete calls the the resourceCache.delete.
I think I am doing something fundamentally wrong though as the current error is:
undefined is not a fuction
This i am sure is because if I try to log out resourceCache.delete it just shows and empty function. The first expect
resourceCache.delete.resolve();
passes ok.
You need to setup resourceCache.delete as a function that returns a promise rather than just set to a promise. You should also be mocking resourceCache.query. To resolve the promise you need to use deffered.resolve(response); after controller.delete is called. Then $rootScope.$digest();.
describe('adminPagesCtrl', function() {
var createController, $rootscope, deferred, resourceCache;
beforeEach(module('jhApp'));
beforeEach(inject(function($controller, _$rootScope_, $q) {
$rootScope = _$rootScope_;
deferred = $q.defer();
resourceCache = {
delete: function () {
},
query: function (page) {
}
};
spyOn(resourceCache, 'delete').and.returnValue(deferred.promise);
createController = function() {
return $controller('adminPagesCtrl', { resourceCache: resourceCache } );
};
}));
it('deletes a page', function() {
//Arrange
var controller = createController();
var page = {
_id: 1
};
var response = {
deleted: true
};
var items = [{
test: 'test'
}];
var expectedDeleteParam = {
id: page._id
};
spyOn(resourceCache, 'query').and.returnValue(items);
//Act
controller.delete(page);
deferred.resolve(response);
$rootScope.$digest();
//Assert
expect(resourceCache.delete).toHaveBeenCalledWith('page', expectedDeleteParam);
expect(resourceCache.query).toHaveBeenCalledWith('page');
expect(controller.items).toEqual(items);
});
});
Plunkr
I finally got this working so in case anyone else has a similar issue here is the amended version. Calling $rootScope.digest() was causing an error with Karma.
Error: Unexpected request: GET views/projects.html
No more request expected
I swapped that for scope = $rootScope.$new(); and now tests are passing.
describe('adminPagesCtrl', function() {
var createController, $rootScope, deferred, resourceCache, scope;
beforeEach(module('jhApp'));
beforeEach(inject(function($controller, $rootScope, $q) {
scope = $rootScope.$new();
deferred = $q.defer();
resourceCache = {
delete: function () {
},
query: function (page) {
}
};
spyOn(resourceCache, 'delete').and.returnValue(deferred.promise);
createController = function() {
return $controller('adminPagesCtrl', { resourceCache: resourceCache } );
};
}));
it('deletes a page', function() {
//Arrange
var controller = createController();
var page = {
_id: 1
};
var response = {
deleted: true
};
var items = [{
test: 'test'
}];
var expectedDeleteParam = {
id: page._id
};
spyOn(resourceCache, 'query').and.returnValue(items);
//Act
controller.delete(page);
deferred.resolve(response);
scope.$digest();
//Assert
expect(resourceCache.delete).toHaveBeenCalledWith('page', expectedDeleteParam);
expect(resourceCache.query).toHaveBeenCalledWith('page');
expect(controller.items).toEqual(items);
});
});

Categories

Resources