I'm discovering Karma great world and I wanted to ask you a simple question about a controller I want to test. Here it is :
angular.module('app', ['ngSanitize'])
.controller('MyController', ['$scope', '$http', function ($scope, $http) {
$scope.myCtrlData = '';
$http.get('../../data/file.json').then(function (res) {
$scope.myCtrlData = res.data.ctrlData;
});
}]);
It simply feed $scope.myCtrlData with a local json file content.
I started to write a quick test but I am stuck when running it.
describe('Test : MyController', function() {
var scope, httpBackend, make;
beforeEach(module('ngSanitize'));
beforeEach(module('app'));
beforeEach(inject(function($rootScope, $controller, $httpBackend){
httpBackend = $httpBackend;
scope = $rootScope.$new();
httpBackend.whenGET('../../data/file.json')
.respond(200, {
'data': { 'ctrlData': 'Yeah' }
});
make = $controller('MyController', { '$scope': scope });
}));
it('should get the data', function() {
httpBackend.flush();
expect(scope.myCtrlData).toBeDefined();
expect(scope.myCtrlData).toEqual('Yeah');
});
});
As a result I get a fail with this log :
PhantomJS 2.1.1 (Mac OS X 0.0.0) BioController should get the data FAILED
Expected undefined to equal 'Yeah'.
/Users/toto/Projects/awesomeproject/tests/views/yeah/test.js:32:30
PhantomJS 2.1.1 (Mac OS X 0.0.0): Executed 1 of 1 (1 FAILED) (0 secs / 0.01 secsPhantomJS 2.1.1 (Mac OS X 0.0.0): Executed 1 of 1 (1 FAILED) ERROR (0.002 secs / 0.01 secs)
Could you please help me to figure out what did I do wrong ?
Thank you very much for your precious help :-)
You need to instantiate the controller.
make = $controller('MyController', { '$scope': scope });
with
$controller('MyController', { '$scope': scope });
Also,
can you modify your code
it('should get the data', function() {
httpBackend.whenGET('../../data/file.json')
.respond(200, {
'data': { 'ctrlData': 'Yeah' }
});
expect(scope.myCtrlData).toBeDefined();
expect(scope.myCtrlData).toEqual('Yeah');
httpBackend.flush();
});
Remove the httpBackend from the beforeEach and check
Related
Test Case File
describe('homeController', function() {
beforeEach(module('moduleInjectionApp'));
var $controller;
var $rootScope;
beforeEach(inject(function(_$controller_, _$rootScope_) {
$controller = _$controller_('homeController', {'$scope': scope});
$rootScope = _$rootScope_;
}));
describe('$scope.ID', function() {
it('Check the scope object', function() {
var $scope = {};
expect($scope.ID).toEqual(5);
});
});
});
Controller File
angular.module('moduleInjectionApp').controller('homeController', homeController)
homeController.$inject = ["$scope", "$rootScope", "$location"];
function homeController($scope, $rootScope, $location) {
console.log("entered homeController")
$scope.ID = 5;
$rootScope.loginObj = JSON.parse(localStorage.getItem('login_data'));
}
Error
Error: Expected undefined to equal 5.
at <Jasmine>
at UserContext.<anonymous> (WebContent/test/home/homeSpec.js:14:31)
at <Jasmine>
Chrome 75.0.3770 (Windows 10.0.0): Executed 1 of 1 (1 FAILED) (0.036 secs / 0.012 secs)
TOTAL: 1 FAILED, 0 SUCCESS
Try
describe('homeController', function() {
beforeEach(module('moduleInjectionApp'));
var $controller;
beforeEach(inject(function(_$controller_){
$controller = _$controller_;
}));
describe('$scope.ID', function() {
it('Check the scope object', function() {
var $scope = {};
var controller = $controller('homeController', { $scope: $scope });
expect($scope.ID).toEqual(5);
});
});
});
When you declare var $scope = {};, you will always get $scope.ID as undefined. You need to do
var $scope = { ID: 5}
Anyways, in unit test, you dont create some values and then expect assertions on it. You validate the values which are already defined or have been modified. Here you were trying to declare and then putting expect (which will always pass)
I am trying to write a simple test in Jasmine to test SearchCtrl. However I am not sure how to structure the unit test because it is my first time using it. This is what I have do so far:
in services.js:
function CategoryService($http) {
this.getCategoryList = function () {
return $http({
method: 'GET',
url: server + '/api/category/',
});
}
}
Sample output:
[
{
"id": "1551742a-5963-4d40-9abb-af7a6fb4ec5d",
"category": "Coach"
}
]
in controller.js:
function SearchCtrl($scope, CategoryService) {
CategoryService.getCategoryList().then(function(dataResponse) {
$scope.categories = dataResponse.data;
});
}
In search.controller.tests.js:
describe('SearchCtrl', function() {
var $controller, scope, categoryServiceMock;
// TODO: Load the app module
beforeEach(module('app.controllers'));
beforeEach(module('app.services'));
// TODO: Instantiate the controller and mocks
beforeEach(inject(function($controller, categoryServiceMock) {
scope = $rootScope.$new();
// mock categoryService
categoryServiceMock = {
login: jasmine.createSpy('category spy')
};
// instantiate SearchCtrl
controller = $controller('SearchCtrl', {
$scope: scope,
'CategoryService': categoryServiceMock
})
}));
describe('#searchList', function() {
// TODO: Call getCategoryList() on the controller
it('should call getCategoryList on categoryService', function() {
expect(categoryServiceMock.getCategoryList).toHaveBeenCalledWith('Coach');
});
describe('once the search is executed', function() {
it('if successful, return a list of categories', function() {
// TODO: Mock response from CategoryService
expect(categoryServiceMock.getCategoryList).toHaveBeenCalledWith('Coach');
});
});
});
});
This currently returns the error:
Error: [$injector:unpr] Unknown provider: categoryServiceMockProvider <- categoryServiceMock
How do I resolve it? Furthermore, have I structured my unit test in the correct way?
UPDATE 25/09
I changed my test to the following:
describe('SearchCtrl', function() {
var scope;
var CategoryServiceMock;
var SearchCtrl;
// Load the app module
beforeEach(module('dingocv.controllers', ['dingocv.services']));
// Define CategoryServiceMock
beforeEach(function() {
CategoryServiceMock = {
getCategoryList: function() {}
};
});
// Inject required services and instantiate the controller
beforeEach(inject(function($rootScope, $controller) {
scope = $rootScope.$new();
SearchCtrl = $controller('SearchCtrl', {
$scope: scope,
CategoryService: CategoryServiceMock
});
}));
// Tests
describe('#searchList', function() {
it('should call registerBook Parse Service method', function () {
spyOn(CategoryServiceMock, 'getCategoryList').andCallThrough();
scope.getCategoryList();
})
});
});
I am now getting this error:
PhantomJS 2.1.1 (Windows 8 0.0.0): Executed 0 of 1 SUCC
PhantomJS 2.1.1 (Windows 8 0.0.0) SearchCtrl #searchList should call registerBook Parse Service method FAILED
forEach#D:/myapp-mobile/www/lib/ionic/js/ionic.bundle.js:13691:24
loadModules#D:/myapp-mobile/www/lib/ionic/js/ionic.bundle.js:17878:12
createInjector#D:/myapp-mobile/www/lib/ionic/js/ionic.bundle.js:17800:30
workFn#D:/myapp-mobile/www/lib/angular-mocks/angular-mocks.js:3074:60
loaded#http://localhost:9876/context.js:151:17
D:/myapp-mobile/www/lib/ionic/js/ionic.bundle.js:17918:53
TypeError: undefined is not a constructor (evaluating 'spyOn(CategoryServiceMock, 'getCategoryList').andCallThrough()') in unit-tests/search.controller.tests.js (line 30)
unit-tests/search.controller.tests.js:30:67
loaded#http://localhost:9876/context.js:151:17
PhantomJS 2.1.1 (Windows 8 0.0.0): Executed 1 of 1 (1 FPhantomJS 2.1.1 (Windows 8 0.0.0): Executed 1 of 1 (1 FAILED) ERROR (0.04 secs / 0.021 secs)
25 09 2016 09:48:38.940:INFO [watcher]: Changed file "D:/myapp-mobile/www/tests/unit-tests/search.controller.tests.js".
PhantomJS 2.1.1 (Windows 8 0.0.0): Executed 0 of 1 SUCCPhantomJS 2.1.1 (Windows 8 0.0.0) SearchCtrl #searchList should call registerBook Parse Service method FAILED
forEach#D:/myapp-mobile/www/lib/ionic/js/ionic.bundle.js:13691:24
loadModules#D:/myapp-mobile/www/lib/ionic/js/ionic.bundle.js:17878:12
createInjector#D:/myapp-mobile/www/lib/ionic/js/ionic.bundle.js:17800:30
workFn#D:/myapp-mobile/www/lib/angular-mocks/angular-mocks.js:3074:60
loaded#http://localhost:9876/context.js:151:17
D:/myapp-mobile/www/lib/ionic/js/ionic.bundle.js:17918:53
TypeError: undefined is not a constructor (evaluating 'spyOn(CategoryServiceMock, 'getCategoryList').andCallThrough()') in unit-tests/search.controller.tests.js (line 30)
unit-tests/search.controller.tests.js:30:67
loaded#http://localhost:9876/context.js:151:17
PhantomJS 2.1.1 (Windows 8 0.0.0): Executed 1 of 1 (1 FPhantomJS 2.1.1 (Windows 8 0.0.0): Executed 1 of 1 (1 FAILED) ERROR (0.035 secs / 0.007 secs)
25 09 2016 09:48:42.307:INFO [watcher]: Changed file "D:/myapp-mobile/www/tests/unit-tests/search.controller.tests.js".
PhantomJS 2.1.1 (Windows 8 0.0.0): Executed 0 of 1 SUCCPhantomJS 2.1.1 (Windows 8 0.0.0) SearchCtrl #searchList should call registerBook Parse Service method FAILED
forEach#D:/myapp-mobile/www/lib/ionic/js/ionic.bundle.js:13691:24
loadModules#D:/myapp-mobile/www/lib/ionic/js/ionic.bundle.js:17878:12
createInjector#D:/myapp-mobile/www/lib/ionic/js/ionic.bundle.js:17800:30
workFn#D:/myapp-mobile/www/lib/angular-mocks/angular-mocks.js:3074:60
loaded#http://localhost:9876/context.js:151:17
D:/myapp-mobile/www/lib/ionic/js/ionic.bundle.js:17918:53
TypeError: undefined is not a constructor (evaluating 'spyOn(CategoryServiceMock, 'getCategoryList').andCallThrough()') in unit-tests/search.controller.tests.js (line 30)
unit-tests/search.controller.tests.js:30:67
loaded#http://localhost:9876/context.js:151:17
PhantomJS 2.1.1 (Windows 8 0.0.0): Executed 1 of 1 (1 FPhantomJS 2.1.1 (Windows 8 0.0.0): Executed 1 of 1 (1 FAILED) ERROR (0.069 secs / 0.008 secs)
You're simply making your tests complex with all those injections. Here's a simple working example for your case:
Service Module
angular.module('dingocv.services', [])
.service('CategoryService', ['$http', function CategoryService($http) {
this.getCategoryList = function () {
//Wasn't sure what server was here. So created a variable here. Feel free to change this.
var server = 'https://www.example.com';
return $http({
method: 'GET',
url: server + '/api/category/',
});
}
}]);
Controller Module
angular.module('dingocv.controllers', ['dingocv.services'])
.controller('SearchCtrl', ['$scope', 'CategoryService', function SearchCtrl($scope, CategoryService) {
CategoryService.getCategoryList().then(function(dataResponse) {
$scope.categories = dataResponse.data;
});
}]);
Tests
describe('SearchCtrl', function() {
var response;
response = {
status: 200,
data: [{
"id": "1551742a-5963-4d40-9abb-af7a6fb4ec5d",
"category": "Coach"
}]
};
//Injecting the modules that would then be used in the tests
beforeEach(module('dingocv.services'));
beforeEach(module('dingocv.controllers'));
beforeEach(inject(function($controller, $rootScope, _CategoryService_) {
$scope = $rootScope.$new();
CategoryService = _CategoryService_;
//Creating a spy for this method so that it doesn't call the original Service method.
spyOn(CategoryService, 'getCategoryList').and.callFake(function(){
return{
then: function(successCallback){
successCallback(response);
}
}
});
SearchCtrl = $controller('SearchCtrl', {
$scope: $scope
});
}));
describe('Initialization', function() {
it('should initialize the controller\'s scope with categories', function(){
expect(CategoryService.getCategoryList).toHaveBeenCalled();
expect($scope.categories).toBeDefined();
expect($scope.categories.length).toEqual(1);
expect($scope.categories[0].id).toEqual(response.data[0].id);
expect($scope.categories[0].category).toEqual(response.data[0].category);
});
});
});
Hope this helps.
I need to test a controller with a lot of injections (services, factories, values), now I have a problem with a value...
I have the following situation:
This is my controller.
(function () {
'use strict';
angular
.module('otpConfigureDatasets')
.controller('otpConfigureDatasetsController', otpConfigureDatasetsController);
otpConfigureDatasetsController.$inject = ['$location', '$state', 'otpWebMapApp', 'otpWMDeltaTracker', 'otpWMStateCache', '$scope', '$timeout', 'otpConfigureDatasetService', 'otpAllDatasetsService', 'otpReviewListDatasetsService','otpConfigureDatasetSelectedTab'];
function otpConfigureDatasetsController($location, $state, otpWebMapApp, otpWMDeltaTracker, otpWMStateCache, $scope, $timeout, otpConfigureDatasetService, otpAllDatasetsService, otpReviewListDatasetsService, otpConfigureDatasetSelectedTab) {
var vm = this;
...
vm.tabs = [{
title: 'MY REVIEW LIST',
selectedAmt: vm.reviewData,
contentTemplate: './app/features/configureDatasets/reviewListDatasets.html',
active: otpConfigureDatasetSelectedTab.tab == 'MyReviewList'
}, {
title: 'ALL DATASETS',
selectedAmt: vm.allData,
contentTemplate: './app/features/configureDatasets/allDatasets.html',
active: otpConfigureDatasetSelectedTab.tab == 'AllDatasets'
}];
...
}
})();
My Spec.js below
'use strict';
describe('Test', function () {
var MainCtrl, scope, _otpWebMapApp_, _otpWMDeltaTracker_, _otpWMStateCache_, _otpConfigureDatasetService_,
_otpAllDatasetsService_, _otpReviewListDatasetsService_, _otpConfigureDatasetSelectedTab_;
beforeEach(function () {
module('otpConfigureDatasets');
});
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
MainCtrl = $controller('otpConfigureDatasetsController', {
$scope: scope, otpWebMapApp: _otpWebMapApp_, otpWMDeltaTracker: _otpWMDeltaTracker_, otpWMStateCache: _otpWMStateCache_,
otpConfigureDatasetService: _otpConfigureDatasetService_, otpAllDatasetsService: _otpAllDatasetsService_,
otpReviewListDatasetsService: _otpReviewListDatasetsService_, otpConfigureDatasetSelectedTab: _otpConfigureDatasetSelectedTab_
});
}));
it('Should be true', function () {
expect(true).toBe(true);
});
});
But when I try to run the test case, It doesn´t work and display the following error:
INFO [karma]: Karma v0.12.37 server started at http://localhost:9876/
INFO [launcher]: Starting browser PhantomJS
INFO [PhantomJS 1.9.8 (Windows 8 0.0.0)]: Connected on socket NHknSeC3p_h_lf12SK Gh with id 27552356
PhantomJS 1.9.8 (Windows 8 0.0.0) Test Should be true FAILED
TypeError: 'undefined' is not an object (evaluating 'otpConfigureDatasetSelectedTab.tab')
at otpConfigureDatasetsController (D:/workspace/.....
I already have all the required files in the karma.config.js....
If I comment these two lines in the controller.
//active: otpConfigureDatasetSelectedTab.tab == 'MyReviewList'
and
//active: otpConfigureDatasetSelectedTab.tab == 'AllDatasets'
The unit testing works fine.
What´s wrong in my Spec definition???
You are not saving the object you're trying to mock. You basically have just
_otpConfigureDatasetService_ = undefined,
_otpReviewListDatasetsService_ = undefined
So it's not the controller that is failing, it's the unit test. You're injecting undefined in place of both objects. I'm not sure what you want to do exactly, but you either need to define some mock objects like _otpConfigureDatasetService_ = {tab: 'something'} or get the real services and adjust them
beforeEach(inject(function ($controller, $rootScope, otpConfigureDatasetService, otpReviewListDatasetsService) {
//
_otpConfigureDatasetService_ = otpConfigureDatasetService;
_otpReviewListDatasetsService_ = otpReviewListDatasetsService;
//
scope = $rootScope.$new();
MainCtrl = $controller('otpConfigureDatasetsController', {
$scope: scope, otpWebMapApp: _otpWebMapApp_, otpWMDeltaTracker: _otpWMDeltaTracker_, otpWMStateCache: _otpWMStateCache_,
otpConfigureDatasetService: _otpConfigureDatasetService_, otpAllDatasetsService: _otpAllDatasetsService_,
otpReviewListDatasetsService: _otpReviewListDatasetsService_, otpConfigureDatasetSelectedTab: _otpConfigureDatasetSelectedTab_
});
}));
BTW the convention is to define _object_ in the inject function and then to save it. If you inject them how they're called then you don't need to use _object_ form for your local variables.
I also prefer to use the $injector
beforeEach( inject(function($injector) {
exampleService = $injector.get('exampleService');
}));
I'm trying to set up testing for Angular with Karma and Jasmine. I've successfully installed and configured Karma, but I'm having trouble using angular-mocks. Below in aTest.spec.js I include a simple app, controller, and test spec to illustrate the problem. Can anyone tell me what I'm doing wrong?
My console output from Karma:
Chrome 39.0.2171 (Mac OS X 10.8.5) ControllerForTest encountered a declaration exception FAILED
TypeError: undefined is not a function
at Suite.<anonymous> (/Users/duncanmalashock/python_projects/scout/public/tests/unit/aTest.spec.js:19:20)
at jasmineInterface.describe (/Users/duncanmalashock/python_projects/scout/node_modules/karma-jasmine/lib/boot.js:59:18)
at /Users/duncanmalashock/python_projects/scout/public/tests/unit/aTest.spec.js:13:1
Chrome 39.0.2171 (Mac OS X 10.8.5): Executed 1 of 1 (1 FAILED) ERROR (0.004 secs / 0.002 secs)
karma.conf.js:
...
files: [
'http://ajax.googleapis.com/ajax/libs/angularjs/1.3.5/angular.min.js',
'angular/vendor/angular-mocks.js',
'tests/unit/*.spec.js'
],
...
Controller:
var testApp = angular.module('testApp', []);
testApp.controller('ControllerForTest', ['$scope',
function($scope) {
$scope.data = {
a: 'foo',
b: 'bar',
c: 'baz'
};
}
]);
aTest.spec.js:
describe('ControllerForTest', function() {
module('testApp');
var $controller;
inject(function(_$controller_) {
$controller = _$controller_;
});
var controller = $controller('ControllerForTest');
it('is defined', function() {
expect($controller).toBeDefined();
});
});
Module instantiation and service injection should happen in a beforeEach, not directly within a describe block. This will make them available to each of the following its.
You also don't need to test $controller, that's an Angular service. Test your controller instead.
describe('ControllerForTest', function() {
var $controller;
var ControllerForTest;
beforeEach(function() {
module('testApp');
inject(function(_$controller_) {
$controller = _$controller_;
});
});
it('is defined', function() {
// This line can also be in the beforeEach.
// Saves having to repetitively instantiate the controller.
ControllerForTest = $controller('ControllerForTest');
expect(ControllerForTest).toBeDefined();
});
});
io with my own angular service. I'm using yeoman and angular workflow: http://yeoman.io/ I need to make io recognised by karma so the test does not fail?
'use strict';
angular.module('publicApp')
.factory('socket', function ($rootScope) {
var socket = io.connect();
return {
on: function on(eventName, callback) {
socket.on(eventName, function () {
var args = arguments;
$rootScope.$apply(function () {
callback.apply(socket, args);
});
});
},
emit: function emit(eventName, data, callback) {
socket.emit(eventName, data, function () {
var args = arguments;
$rootScope.$apply(function () {
if (callback) {
callback.apply(socket, args);
}
});
});
}
};
});
angular.module('publicApp')
.controller('MainCtrl', function ($scope, socket) {
$scope.awesomeThings = [
'HTML5 Boilerplate',
'AngularJS',
'Karma'];
socket.on('person:added', function (data) {
$scope.person = data;
});
});
angular.module('publicApp', [])
.config(function ($routeProvider) {
$routeProvider.when('/', {
templateUrl: 'views/main.html',
controller: 'MainCtrl'
})
.otherwise({
redirectTo: '/'
});
});
'use strict';
describe('Controller: MainCtrl', function () {
// load the controller's module
beforeEach(module('publicApp'));
var MainCtrl,
scope;
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
MainCtrl = $controller('MainCtrl', {
$scope: scope
});
}));
it('should attach a list of awesomeThings to the scope', function () {
expect(scope.awesomeThings.length).toBe(3);
});
});
io is part of window, I've tried using angulars $window object but had no luck. The error I'm getting from karma is:
Running "karma:unit" (karma) task
WARN [karma]: Port 8080 in use
INFO [karma]: Karma v0.10.2 server started at http://localhost:8081/
INFO [launcher]: Starting browser Chrome
WARN [watcher]: Pattern "/Users/michaeljames/Documents/Projects/StackOverflow/public/test/mock/**/*.js" does not match any file.
INFO [Chrome 29.0.1547 (Mac OS X 10.8.2)]: Connected on socket LmbsWIC-97zMEi76FmiE
Chrome 29.0.1547 (Mac OS X 10.8.2) Controller: MainCtrl should attach a list of awesomeThings to the scope FAILED
ReferenceError: io is not defined
at Object.$get (/Users/michaeljames/Documents/Projects/StackOverflow/public/app/scripts/services/socket.js:5:16)
at Object.invoke (/Users/michaeljames/Documents/Projects/StackOverflow/public/app/bower_components/angular/angular.js:3000:28)
at /Users/michaeljames/Documents/Projects/StackOverflow/public/app/bower_components/angular/angular.js:2838:37
at getService (/Users/michaeljames/Documents/Projects/StackOverflow/public/app/bower_components/angular/angular.js:2960:39)
at invoke (/Users/michaeljames/Documents/Projects/StackOverflow/public/app/bower_components/angular/angular.js:2978:13)
at Object.instantiate (/Users/michaeljames/Documents/Projects/StackOverflow/public/app/bower_components/angular/angular.js:3012:23)
at /Users/michaeljames/Documents/Projects/StackOverflow/public/app/bower_components/angular/angular.js:4981:24
at null.<anonymous> (/Users/michaeljames/Documents/Projects/StackOverflow/public/test/spec/controllers/main.js:14:16)
at Object.invoke (/Users/michaeljames/Documents/Projects/StackOverflow/public/app/bower_components/angular/angular.js:3000:28)
at workFn (/Users/michaeljames/Documents/Projects/StackOverflow/public/app/bower_components/angular-mocks/angular-mocks.js:1795:20)
Error: Declaration Location
at window.jasmine.window.inject.angular.mock.inject (/Users/michaeljames/Documents/Projects/StackOverflow/public/app/bower_components/angular-mocks/angular-mocks.js:1781:25)
at null.<anonymous> (/Users/michaeljames/Documents/Projects/StackOverflow/public/test/spec/controllers/main.js:12:14)
at /Users/michaeljames/Documents/Projects/StackOverflow/public/test/spec/controllers/main.js:3:1
TypeError: Cannot read property 'length' of undefined
at null.<anonymous> (/Users/michaeljames/Documents/Projects/StackOverflow/public/test/spec/controllers/main.js:20:31)
Chrome 29.0.1547 (Mac OS X 10.8.2): Executed 1 of 1 (1 FAILED) ERROR (0.306 secs / 0.067 secs)
Warning: Task "karma:unit" failed. Use --force to continue.
Aborted due to warnings.
I'd mock the entire socket service, since this is a means of going to some sort of server and these unit tests should be about testing your UI code only (the reason for $httpBackend in ngMock).
Here are some clever guys that figured it out:
https://github.com/btford/angular-socket-io-seed/issues/4#issuecomment-14505212.
In your karma.config.js files section inject the server side socket.io.js
files: [
'test/unit/**/*.js',
'any/other/included/files/*.js',
'http://localhost:8080/socket.io/socket.io.js' //just only inject it
],