I have a service that adds/removes classes to a couple of HTML elements.
I am trying to test these changes depending on which method is called.
define(['require', 'angular'], function (require, angular) {
'use strict';
var myFactory = function () {
var header = angular.element("#app-header");
var footer = angular.element(document.getElementsByClassName("app-footer"));
var change = false;
return {
red: function() {
header.addClass("alert-warning");
footer.removeClass("notify");
change = true;
},
black: function() {
if (change) {
this.red();
}
}
};
};
return myFactory;
});
I ahve tried:
describe('<-- MyFactory Spec ------>', function () {
var myFactory, $compile, scope;
beforeEach(angular.mock.module('MyApp'));
beforeEach(inject(function(_myFactory_, _$compile_, _$rootScope_){
myFactory = _myFactory_;
$compile = _$compile_;
scope = _$rootScope_.$new();
}));
it('should open the menu', function(){
var header = angular.element("#app-header");
header = $compile(header)(scope);
scope.$digest();
myFactory.red();
scope.$apply();
expect(header).toHaveClass('alert-warning');
expect(change).toBeTruthy();
});
});
With the above, i get error:
TypeError: 'undefined' is not a function (evaluating 'expect(header).toHaveClass('alert-warning')')
I suspect you aren't pulling in jasmine-jquery matchers.
.toHaveClass(...)
Is not a standard Jasmine matcher, you need to add it with jasmine-jquery
Related
I called one $mdDialog inside a function. I want to unit-test $mdDialog ok and cancel cases.
The below is my controller code (app.controller.js).
(function () {
'use strict';
app.controller('AppCtrl', AppCtrl);
AppCtrl.$inject = ['$scope', '$mdDialog'];
function AppCtrl($scope, $mdDialog) {
$scope.saveEntry = function (ev) {
var confirm = $mdDialog.prompt()
.title('Save Entry')
.textContent('If you want, you can add a description to explain what you changed.')
.placeholder('Version Description')
.ariaLabel('Version Description')
.initialValue('')
.targetEvent(ev)
.ok('Save')
.cancel('Cancel');
$mdDialog.show(confirm).then(function (result) {
$scope.status = true;
}, function () {
$scope.status = false;
});
};
}
})();
The following is the spec code (app.controller.spec.js) :
describe('Unit test AppController: mdDialog', function () {
var $controller, $mdDialog;
beforeEach(function () {
module('App');
inject(function (_$controller_, _$mdDialog_) {
$controller = _$controller_;
$mdDialog = _$mdDialog_;
});
});
it(': Opened', function () {
var $scope = {};
var controller = $controller('AppCtrl', { $scope: $scope });
var $mdDialogOpened = false;
$mdDialog.show = jasmine.createSpy().and.callFake(function () {
$mdDialogOpened = true;
});
$scope.saveEntry();
$scope.$digest();
expect($mdDialog.show).toHaveBeenCalled;
expect($mdDialogOpened).toBe.true;
});
});
when I running the above code I'm getting the following error:
TypeError: Cannot read property 'then' of undefined
I referred this GitHub issue https://github.com/angular/material/issues/1482. But I'm not getting solution for my problem
Thanks in advance
The problem is that you are injecting one version of $mdDialog, and trying to test on another one.
You could try something like this:
describe('Unit test AppController: mdDialog', function () {
var ctrl, mdDialog, scope;
beforeEach(function () {
module('App');
inject(function ($rootScope, $controller, $mdDialog) {
scope = $rootScope.$new();
mdDialog = $mdDialog; //keep the reference, for later testing.
spyOn(mdDialog, 'show');
mdDialog.show.and.callFake(function () {
return {
then: function (callBack) {
callBack(true); //return the value to be assigned.
}
}
});
ctrl = $controller('AppCtrl',{$scope:scope, $mdDialog:mdDialog}); //Inject the dependency
});
});
it(': Opened', function () {
scope.saveEntry(); //exercise the method.
scope.$digest();
expect(mdDialog.show).toHaveBeenCalled();
expect(scope.status).toBe(true);
});
});
Something very similar should work.
hope this help.
I am writing unit tests for my existing AngularJS app. There are just four methods in this service. I was able to getFollowUpList to work, but refresh() is not working and it is a very simple method.
The refresh method should simply set deferredGetFollowUpList = null and return true in my test.
There error I'm getting is: TypeError: Cannot read property 'then' of undefined, so my refresh method is undefined. Why is this the case? Thanks
Service
(function () {
"use strict";
angular
.module("all.patient.details")
.factory("followUpListService", ["$rootScope", "$http", "userContext", "$q", "$uibModal", "htmlBaseUrl", FollowUpListService]);
function FollowUpListService($rootScope, $http, userContext, $q, $uibModal, htmlBaseUrl) {
var deferredGetFollowUpList = null;
return {
getFollowUpList: getFollowUpList,
displayModal: displayModal,
refresh: refresh,
save: save
}
function refresh() {
deferredGetFollowUpList = null;
}
}
})();
Unit Test
describe("followUpListService", function () {
beforeEach(module("all.patient.details"));
var followUpListService = {};
var $httpBackend;
var htmlBaseUrlMock;
var returnedFollowUpListData;
var deferredGetFollowUpList;
var $rootScope;
var $q;
var $uibModal;
beforeEach(function () {
htmlBaseUrlMock = { format: function () { } };
module(function ($provide) {
$provide.value("htmlBaseUrl", htmlBaseUrlMock);
});
inject(function (_$rootScope_, _$httpBackend_, _$q_, _$uibModal_, _followUpListService_) {
$rootScope = _$rootScope_;
$httpBackend = _$httpBackend_;
$q = _$q_;
$uibModal = _$uibModal_;
followUpListService = _followUpListService_;
});
});
afterEach(function () {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it("calls refresh()", function () {
followUpListService.refresh()
.then(function (data) {
deferredGetFollowUpList = data;
});
expect(deferredGetFollowUpList).toBe(null);
});
As deferredGetFollowUpList is service level variable, Can you write your test as -
followUpListService.deferredGetFollowUpList = data; //Any Mock Data
followUpListService.refresh();
expect(followUpListService.deferredGetFollowUpList).toBe(null);
So Im trying to figure out how to write unit tests for my angular controller. I am using karma as my runner. I was able to write 1 successful test but every time I try to write another test it yells at me about unexpected calls and such.
Here is my controller im trying to test.
(function (angular) {
'use strict';
var ngModule = angular.module('myApp.dashboardCtrl', []);
ngModule.controller('dashboardCtrl', function ($scope, $http) {
//"Global Variables"
var vm = this;
vm.success = false;
vm.repos = [];
//"Global Functions"
vm.addRepository = addRepository;
vm.listRepos = listRepos;
//Anything that needs to be instantiated on page load goes in the init
function init() {
listRepos();
}
init();
// Add a repository
function addRepository(repoUrl) {
$http.post("/api/repo/" + encodeURIComponent(repoUrl)).then(function (){
vm.success = true;
vm.addedRepo = vm.repoUrl;
vm.repoUrl = '';
listRepos();
});
}
//Lists all repos
function listRepos() {
$http.get('/api/repo').then( function (response){
vm.repos = response.data;
});
}
});
}(window.angular));
So I have a test written for listRepos(). It goes as follows
describe('dashboardCtrl', function() {
var scope, httpBackend, createController;
// Set up the module
beforeEach(module('myApp'));
beforeEach(inject(function($rootScope, $httpBackend, $controller) {
httpBackend = $httpBackend;
scope = $rootScope.$new();
createController = function() {
return $controller('dashboardCtrl', {
'$scope': scope
});
};
}));
afterEach(function() {
httpBackend.verifyNoOutstandingExpectation();
httpBackend.verifyNoOutstandingRequest();
});
it('should call listRepos and return all repos from the database', function() {
var controller = createController();
var expectedResponse = [{id: 12345, url: "https://github.com/myuser/myrepo.git"}];
httpBackend.expect('GET', '/api/repo')
.respond(expectedResponse);
httpBackend.flush();
scope.$apply(function() {
scope.listRepos;
});
expect(controller.repos).toEqual(expectedResponse);
});
This works and the test passes. Now my problem is I want to write another test to test the other function that calls a new api endpoint.
This is the test im trying to write for addRepository.
it('should addRepository to the database', function() {
var controller = createController();
var givenURL = "https://github.com/myuser/myURLtoMyRepo.git";
httpBackend.expect('POST', '/api/repo/' + encodeURIComponent(givenURL)).respond('success');
httpBackend.flush();
scope.$apply(function() {
scope.addRepository(givenURL);
});
expect(controller.success).toBe(true);
expect(controller.listRepos).toHaveBeenCalled();
});
The error I get when I add this test to the spec is:
Error: Unexpected request: GET /api/repo
Expected POST /api/repo/https%3A%2F%2Fgithub.com%2Fmyuser%2FmyURLtoMyRepo.git
at $httpBackend
Error: [$rootScope:inprog] $digest already in progress
http://errors.angularjs.org/1.4.8/$rootScope/inprog?p0=%24digest
The example I am working with is this one here
Any suggestions or tips is greatly appreciated!
UPDATE:
So changed my function to return the promise from the $http.post,
I rewrote my 2nd test and also wrapped my first test in a describe block describing the function its trying to test.
With the following:
describe('addRepository', function () {
it('should addRepository to the database', function () {
var controller = createController();
var givenURL = "https://github.com/myuser/myURLtoMyRepo.git";
httpBackend.expect('POST', '/api/repo/' + encodeURIComponent(givenURL)).respond('success');
scope.$apply(function () {
scope.addRepository(givenURL);
});
httpBackend.flush();
expect(controller.success).toBe(true);
});
it('should call listRepos', function() {
var controller = createController();
httpBackend.expect('GET', '/api/repo').respond('success');
controller.controller().then(function (result) {
expect(controller.listRepos).toHaveBeenCalled();
});
httpBackend.flush();
});
});
I still get the error:
Error: Unexpected request: GET /api/repo
Expected POST /api/repo/https%3A%2F%2Fgithub.com%2Fmyuser%2FmyURLtoMyRepo.git
at $httpBackend
Error: [$rootScope:inprog] $digest already in progress
but also
TypeError: 'undefined' is not a function (evaluating 'controller.controller()')
Error: Unflushed requests: 1
which shows 2 tests failed.
The flush should come after the call to the function. I'd also change the function to return the promise from the $http.post:
// Add a repository
function addRepository(repoUrl) {
return $http.post("/api/repo/" + encodeURIComponent(repoUrl)).then(function (){
vm.success = true;
vm.addedRepo = vm.repoUrl;
vm.repoUrl = '';
listRepos();
});
}
And then in the test you can call it and test the success part:
EDIT
I changed the controller.controller() to what you have.
it('should call listRepos', function() {
// Your setup
ctrl.addRepository().then(function(result) {
expect(ctrl.listRepos).toHaveBeenCalled();
});
});
EDIT 2
I emulated as best i could your code and the tests I write for the code:
(function () {
'use strict';
angular
.module('myApp')
.controller('DashboardController',DashboardController);
DashboardController.$inject = ['$http'];
function DashboardController($http) {
var vm = this;
vm.success = false;
vm.repos = [];
vm.addRepository = addRepository;
vm.listRepos = listRepos;
init();
// Anything that needs to be instantiated on page load goes in the init
function init() {
vm.listRepos();
}
// Add a repository
function addRepository(repoUrl) {
return $http.post('http://jsonplaceholder.typicode.com/posts/1.json').then(function (){
vm.success = true;
vm.addedRepo = vm.repoUrl;
vm.repoUrl = '';
vm.listRepos();
});
}
// Lists all repos
function listRepos() {
return $http.get('http://jsonplaceholder.typicode.com/posts/1').then( function (response){
vm.repos = response.data;
});
}
};
}());
Here I'm using an online JSONPlaceholder API to simulate HTTP calls as I, obviously, can't hit what you're pointing at. And for the test (which all pass):
(function() {
'use strict';
fdescribe('DashBoardController', function() {
var $rootScope, scope, ctrl, $httpBackend;
beforeEach(module('myApp'));
beforeEach(inject(function(_$rootScope_, _$httpBackend_,$controller) {
$rootScope = _$rootScope_;
scope = $rootScope.$new();
$httpBackend =_$httpBackend_;
ctrl = $controller('DashBoardController',{
$scope: scope
});
}));
beforeEach(function() {
// Setup spies
spyOn(ctrl,'listRepos');
});
describe('controller', function() {
it('should be defined', function() {
expect(ctrl).toBeDefined();
});
it('should initialize variables', function() {
expect(ctrl.success).toBe(false);
expect(ctrl.repos.length).toBe(0);
});
});
describe('init', function() {
it('should call listRepos', function() {
$httpBackend.expectGET('http://jsonplaceholder.typicode.com/posts/1')
.respond({success: '202'});
$httpBackend.expectPOST('http://jsonplaceholder.typicode.com/posts/1.json')
.respond({success: '202'});
ctrl.addRepository().then(function(result) {
expect(ctrl.success).toBe(true);
expect(ctrl.repoUrl).toBe('');
expect(ctrl.listRepos).toHaveBeenCalled();
});
$httpBackend.flush();
});
});
});
}());
I've been staring at this for hours but I don't understand what's wrong with it. It just tells me that the addTodo method doesn't exist when I've defined it in the controller file - what am I missing here?
main.js
angular.module('mytodoApp')
.controller('MainCtrl', function ($scope, localStorageService) {
// breaks on repeat or blank input
function addTodoFn() {
$scope.todos.push($scope.todo);
$scope.todo = '';
}
function removeTodoFn(index) {
$scope.todos.splice(index, 1);
}
function watchFn() {
localStorageService.set('todos', $scope.todos);
}
//////////
var todosInStore = localStorageService.get('todos');
$scope.todos = todosInStore || [];
$scope.$watch('todos', watchFn, true);
$scope.addTodo = addTodoFn;
$scope.removeTodo = removeTodoFn;
});
main.spec.js
describe('Controller: MainCtrl', function () {
var MainCtrl;
var scope;
var store = [];
var todo;
var localStorage = {
addItem: function() {
store.push(todo);
},
deleteItem: function(index) {
store.splice(index, 1);
}
};
beforeEach(function(){
module('mytodoApp');
});
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
MainCtrl = $controller('MainCtrl as mc', {
$scope: scope
});
// LocalStorage mock.
spyOn(MainCtrl, 'addTodo').and.callFake(localStorage.addItem); <-- throwing the error
spyOn(MainCtrl, 'removeTodo').and.callFake(localStorage.deleteItem);
}));
afterEach(function() {
store = [];
todo = '';
});
it('should have no items to start with', function() {
expect(store.length).toBe(0);
});
it('should add items to the list', function() {
todo = 'a';
scope.addTodo();
expect(store.length).toBe(1);
todo = 'b';
scope.addTodo();
expect(store.length).toBe(2);
});
});
Error
PhantomJS 1.9.8 (Mac OS X 0.0.0) Controller: MainCtrl should add items to the list FAILED
Error: addTodo() method does not exist at /Users/Baggio/mytodo/test/spec/controllers/main.js:29
You're spying the addTodo method of MainCtrl. But MainCtrl doesn't have such a method. The method is on the scope, not on MainCtrl.
BTW, if you spied on scope, your test would test anything: it would only test that the Jasmin spy does what it's supposed to do, instead of testing that the actual addTodo() method does what it's supposed to do. What you should be spying or mocking are the dependencies of the controller, i.e. the localStorage service.
So I am trying to learn how to unit test with Jasmine with Angular. I have got a number of unit tests working but this one has stumped me. I took out the alerts array in my test you can make it any array.. But how to mock this and getting this to work has really stumped me. I would think that the object would exist.
(function () {
var app = angular.module("TabDirectives", ["Communication"]);
app.directive("sentAlerts", ["DirectiveProvider", "DataProvider", function (DirectiveProvider, DataProvider) {
var dir = DirectiveProvider("SentAlerts");
dir.controller = function () {
var ctrl = this;
DataProvider("AlertsByDate").then(function (Result) {
ctrl.AlertList = Result.data;
});
};
dir.controllerAs = "Alerts"
return dir;
}]);
})()
I have a test that looks like
describe("Tab Directive Unit Tests", function () {
var controller;
describe("Tab Directive Default Values", function () {
beforeEach(inject(function ($rootScope, $compile) {
element = angular.element("<sent-alerts></sent-alerts>");
$compile(element)($rootScope.$new());
$rootScope.$digest();
controller = element.controller;
}));
it("Ctrl should be this", function () {
expect(controller.ctrl).toBe(controller.this);
});
it("AlertList should have Alerts", function () {
expect(controller.ctrl.AlertList).toBe(alerts);
});
});
});
The error I'm getting looks like
TypeError: Cannot read property 'AlertList' of undefined
You have to initialize and inject your controller as well. Something like this:
var $controller;
var $rootScope;
var scope;
var controller;
beforeEach(inject(function (_$controller_, _$rootScope_) {
$controller = _$controller_;
$rootScope = _$rootScope_;
scope = $rootScope.$new();
controller = $controller('ScopeController', { '$scope': scope });
}));