I have a simple controller in my angular app, and the jasmine test spec for the same returns a Reference Error.
My Controller code:
'use strict';
angular.module('taskListAppApp')
.controller('MainCtrl', function ($scope) {
$scope.todoList = [{
todoText: 'In case of Fire',
done: false
}, {
todoText: 'git commit',
done: false
}, {
todoText: 'git push',
done: false
}, {
todoText: 'exit the building!',
done: false
}];
$scope.getTotalTodos = function () {
return $scope.todoList.length;
};
$scope.todoAdd = function () {
// Checking for null or empty string
if (null !== $scope.taskDesc && "" !== $scope.taskDesc) {
$scope.todoList.push({
todoText: $scope.taskDesc,
done: false
});
}
};
// Function to remove the list items
$scope.remove = function () {
var oldList = $scope.todoList;
$scope.todoList = [];
angular.forEach(oldList, function (x) {
if (!x.done) {
$scope.todoList.push(x);
}
});
};
});
And my test spec:
"use strict"
describe('Controller: MainCtrl', function () { //describe your object type
// beforeEach(module('taskListNgApp2App')); //load module
beforeEach(angular.mock.module('taskListAppApp'));
describe('MainCtrl', function () { //describe your app name
var todoCtrl2;
beforeEach(inject(function ($controller, $rootScope) {
var scope = $rootScope.$new();
todoCtrl2 = $controller('MainCtrl', {
//What does this line do?
$scope: scope
});
}));
it('should have todoCtrl defined', function () {
expect(todoCtrl2).toBeDefined();
});
it('trial test for toEqual', function(){
var a = 4;
expect(a).toEqual(4);
});
//THESE 2 FAIL
it('should have todoList defined', function() {
expect(scope.todoList).toBeDefined();
});
it('should have add method defined', function(){
expect(todoCtrl2.todoAdd()).toBeDefined();
});
});
});
The error I get is:
PhantomJS 2.1.1 (Linux 0.0.0) Controller: MainCtrl MainCtrl should have add method defined FAILED
TypeError: undefined is not a function (evaluating 'todoCtrl2.todoAdd()') in test/spec/controllers/main.spec.js (line 58)
test/spec/controllers/main.spec.js:58:28
loaded#http://localhost:8080/context.js:151:17
PhantomJS 2.1.1 (Linux 0.0.0): Executed 4 of 4 (2 FAILED) (0.05 secs / 0.02 secs)
I tried other ways to call the objects/functions, but the last 2 tests are failing every time with the same error viz. ReferenceError
Where am I going in calling the objects?
You need to declare var scope outside the function. your scope variable is undefined in your test case.
Try this
describe('Controller: MainCtrl', function () { //describe your object type
var scope;
// beforeEach(module('taskListNgApp2App')); //load module
beforeEach(angular.mock.module('taskListAppApp'));
describe('MainCtrl', function () { //describe your app name
var todoCtrl2;
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
todoCtrl2 = $controller('MainCtrl', {
//What does this line do?
$scope: scope
});
}));
it('should have todoCtrl defined', function () {
expect(todoCtrl2).toBeDefined();
});
it('trial test for toEqual', function(){
var a = 4;
expect(a).toEqual(4);
});
//THESE 2 FAIL
it('should have todoList defined', function() {
expect(scope.todoList).toBeDefined();
});
it('should have add method defined', function(){
expect(scope.todoAdd).toBeDefined();
});
});
});
Related
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 have simple controller where I want to test mechanics inside of a promise (in this case, I want to test that foo was called when I run bar. Here's my controller:
angular.module('myModule', [])
.controller('MyCtrl', function ($q) {
var myPromise = $q.when();
this.foo = function () {
console.log('running foo');
};
this.bar = function () {
myPromise.then(function () {
this.foo();
});
};
});
And here's my jasmine test:
describe('MyCtrl', function () {
var $controller, $scope, $q;
beforeEach(inject(function ($rootScope, _$q_, _$controller_) {
$controller = _$controller_;
$q = _$q_;
$scope = $rootScope.$new();
}));
describe('bar function', function () {
it('should call the foo function', function () {
var controller = $controller('MyCtrl', { $q: $q });
spyOn(controller, 'foo');
controller.bar();
$scope.$digest();
expect(controller.foo).toHaveBeenCalled();
});
});
});
When I run this test, I get this error:
TypeError: 'undefined' is not an object (evaluating 'this.foo')
It seems that inside the then() function block, I lose invocation context referring to the controller. When the test runs and hits this.foo(), this is undefined.
'this' doesn't contain an attribute 'foo' because the context (to the outer scope) is not bound.
You could do one of the following:
1.
this.bar = function() {
var that = this;
myPromise.then(function () {
that.foo();
});
};
2.
this.bar = function() {
function onSuccess() { this.foo(); }
myPromise.then(onSuccess.bind(this));
};
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.
This is one of the functions in my controller
function sendMeetingInvitation(companyId, meetingId) {
meetingService.sendInvitations(companyId, meetingId)
.success(function() {
$state.go('company.meeting.view', {}, {
reload: true
});
})
.error(function() {
//more code for error handling
});
}
Below is the test case I'm using to test when we call the sendMeetingInvitation(), whether it should invoke the to the success() block of the service call of meetingService.sendInvitations
describe('EditMeetingCtrl.sendMeetingInvitation()', function() {
var $rootScope, scope, $controller, $q, companyService, meetingService;
var mockedHttpPromise = {
success: function() {}
};
beforeEach(angular.mock.module('MyApp'));
beforeEach(angular.mock.inject(function(_$httpBackend_, _companyService_, _meetingService_) {
$httpBackend = _$httpBackend_;
companyService = _companyService_;
meetingService = _meetingService_;
}));
beforeEach(inject(function($rootScope, $controller, _meetingService_) {
scope = $rootScope.$new();
createController = function() {
return $controller('EditMeetingCtrl', {
$scope: scope,
meeting: {},
meetingService: _meetingService_
});
};
var controller = new createController();
}));
it("should should send invitations", function() {
spyOn(meetingService, 'sendInvitations').and.returnValue(mockedHttpPromise);
scope.sendMeetingInvitations(123456, 123456);
expect(meetingService.sendInvitations).toHaveBeenCalledWith(123456, 123456);
});
});
I get this error which is not really helpful .
PhantomJS 1.9.8 (Windows 8) In EditMeetingCtrl EditMeetingCtrl.sendMeetingInvitation() should should send invitations FAILED
TypeError: 'undefined' is not an object (near '...})...')
What am I doing here wrong? I tried my mockedHttpPromise to following . but same result
var mockedHttpPromise = {
success: function() {},
error: function() {}
};
The function sendInvitations expects to return a promise, so, what you need to do is to create a defer and return it, like this:
-First of all you need to inject $q: $q = $injector.get('$q');
-Create a defer: deferred = $q.defer();
Your function mockedHttpPromise, should look like:
function mockedHttpPromise() {
deferred = $q.defer();
return deferred.promise;
}
And inside your test:
it("should should send invitations", function() {
spyOn(meetingService, 'sendInvitations').and.returnValue(mockedHttpPromise);
scope.sendMeetingInvitations(123456, 123456);
deferred.resolve({});
scope.$apply();
expect(meetingService.sendInvitations).toHaveBeenCalledWith(123456, 123456);
});
And to test error block, change deferred.resolve to deferred.reject
The following test passes whether my expect statement is
expect(propertyFactory.readProperty).should.have.been.called;
or
expect(propertyFactory.readProperty).should.have.not.been.called;
I've tried this with or without the sinon stub.
describe('pf.propertyModule', function () {
var controller;
var propertyFactory;
beforeEach(module('pf.core'));
beforeEach(module('pf.propertyModule'));
beforeEach(inject(function(_propertyFactory_) {
propertyFactory = _propertyFactory_;
/*
sinon.stub(propertyFactory, 'readProperty', function() {
return {
data: {
property: 'xyz'
}
}
});*/
}));
describe('readPropertyCtrl', function () {
it('should have a passing first test', function () {
expect(propertyFactory.readProperty).should.have.been.called;
});
});
});
UPDATE: Ok, so I got past the initial "non-error" by ensuring "sinon-chai" was installed, but now I'm getting the output: thrown: [Function] } is not a spy or a call to a spy!
UPDATE: Ok, so I got the previous error working by calling sinon.spy(). However, now I'm wondering how one would go about mocking a method that is expected to return a promise. I'm getting the error " TypeError: 'undefined' is not an object (evaluating 'propertyFactory.readProperty({id: $stateParams.id}).then')
"
Updated code:
describe('pf.propertyModule', function () {
var ctrl;
var scope;
var propertyFactory;
beforeEach(module('pf.core'));
beforeEach(module('pf.propertyModule'));
beforeEach(inject(function($rootScope, $controller) {
scope = $rootScope.$new();
propertyFactory = {
readProperty: sinon.spy()
};
ctrl = $controller('readPropertyCtrl as vm', {
$scope: scope,
propertyFactory: propertyFactory
});
}));
describe('readPropertyCtrl', function () {
it('should have a passing first test', function () {
expect(scope.vm).to.be.defined;
expect(propertyFactory.readProperty).should.have.been.called;
});
});
});
Code to test:
propertyFactory.readProperty({id: $stateParams.id}).then(getPropertySuccess);
function getPropertySuccess(response) {
vm.loadedProperty = response.data.property;
}
Updated Code:
describe('pf.propertyModule', function () {
var ctrl;
var scope;
var propertyFactory;
beforeEach(module('pf.core'));
beforeEach(module('pf.propertyModule'));
beforeEach(inject(function($rootScope, $controller, $q, _propertyFactory_) {
scope = $rootScope.$new();
propertyFactory = _propertyFactory_;
var deferred = $q.defer();
var promise = deferred.promise;
sinon.stub(propertyFactory, "readProperty").returns(promise);
deferred.resolve({property: 'xyz'});
ctrl = $controller('readPropertyCtrl as vm', {
$scope: scope,
propertyFactory: propertyFactory
});
}));
describe('readPropertyCtrl', function () {
it('should have a passing first test', function () {
expect(scope.vm).to.be.defined;
expect(propertyFactory.readProperty).should.have.been.called;
});
});
});
This attempt is returning thrown: [Function] } is not a spy or a call to a spy!