Okay, so Im trying to implement unit tests for an AngularJS project using Jasmine 2.2.0 - and I am not able to get a basic example test working for a controller. Any help is appreciated.
Here is the code for the controller (the simplest one I have):
angular.module('app.alerts')
.controller('AlertCtrl', AlertCtrl);
AlertCtrl.$inject = ['$scope', 'AlertService'];
function AlertCtrl($scope, AlertService) {
var vm = this;
vm.closeAlert = function(index) {
AlertService.closeAlert(index);
}
vm.getAlertList = function() {
return AlertService.getAlertList();
}
}
And here is the spec file I am trying to run:
describe('myApp', function() {
describe('controller', function() {
var scope, controller;
var mockAlertService = { // simple mock service
closeAlert: function(e) {
console.log('close');
},
getAlertList: function() {
return [];
}
}
beforeEach(function() {
angular.mock.module('app.alert');
});
beforeEach(inject(function($rootScope, $controller) {
scope = $rootScope.$new();
controller = $controller('AlertCtrl as alertctrl', {
$scope: scope,
AlertService: mockAlertService
});
}));
it('should work: ', function() {
expect(true).toBe(true);
});
});
});
When I run the test I get the following error message:
Error: [$inject:modulerr] Failed to instantiate module app.alert due to:
[$injector:nomod] Module 'app.alert' is not available! You either misspelled
the module name or forgot to load it.
It seems that the injector doesn't know about the app.alert module, which means its not being properly mocked? Im including the angular-mocks.js file in my SpecRunner.html file. Can anybody see what Im doing wrong?
Haven't you misspelled app.alert and app.alerts:
angular.module('app.alerts')
.controller('AlertCtrl', AlertCtrl);
and
angular.mock.module('app.alert');
Related
I have a simple service implemented like this
sameRoof
.factory('dbService', function (localStorageService, backendUpdate) {
return {
checkProfileAndFlat: function () {
return (localStorageService.get('profile') && localStorageService.get('flatshare'));
}
};
});
LocalStorage are ngModules installed with bower.
I am writint unit test
'use strict';
describe('Service: service taking care of asking the local database', function () {
var localStorageService;
var fakeDB = {'profile' : 'testProfile', 'flatshare' : 'flatshare'};
// load the service's module
beforeEach(module('frontApp'));
// instantiate service
var dbService;
beforeEach(inject(function (_dbService_, _localStorageService_) {
dbService = _dbService_;
localStorageService = _localStorageService_;
//mock localStorageService get/add
spyOn(localStorageService,'get').andCallFake(function(key){
return fakeDB[key];
});
}));
it('should check profile and flatshare', function () {
console.log(localStorageService.get('profile'));
expect( dbService.checkProfileAndFlat() ).toBe(false);
});
});
but i am having problems here,
TypeError: 'undefined' is not a function (evaluating 'spyOn ...)
seems like i am implementing in wrong way the spyOn
the answer is
//mock localStorageService get/add
spyOn(localStorageService,'get').and.callFake(function(key){
return fakeDB[key];
});
as i am using jasmine 2.3.4 and jasmine API has changed compared to the one 1.3
The following service uses $q.when to wrap a third-party promise:
// service.js
angular.module('test', [])
.service('pouchdb', function($q, $window) {
var db = new $window.PouchDB('test');
this.info = function() {
return $q.when(db.info.apply(db, arguments));
};
});
Corresponding unit test:
describe('Failing Q when tests', function() {
beforeEach(module('test'));
var $rootScope, pouchdb;
beforeEach(inject(function(_$rootScope_, pouchdb) {
$rootScope = _$rootScope_;
pouchdb = pouchdb;
}));
it('should resolve a promise', function(done) {
// FIXME: never resolves
pouchdb.info()
.then(function(info) {
expect(info).toBeDefined();
})
.finally(done);
$rootScope.$apply();
});
});
pouchdb.info never resolves and Jasmine times out. However, if I manually inject ng, the spec works as expected:
describe('Working Q when tests', function() {
var pouchdb;
beforeEach(function() {
var $injector = angular.injector(['ng', 'test']);
var pouchDB = $injector.get('pouchdb');
pouchdb = pouchDB('db');
});
it('should resolve a promise', function(done) {
pouchdb.info()
.then(function(info) {
expect(info).toBeDefined();
})
.finally(done);
});
});
Could anyone explain why;
The first spec doesn't resolve
The second spec does (injecting ng)
It doesn't need $rootScope.$apply
Whether it's a good pattern to use
Are you using angular-mocks? https://docs.angularjs.org/api/ngMock
The only reason why I think that you'd need to inject 'ng' manually is if there is no ng-app initializing your app, at least according to https://docs.angularjs.org/api/ng/function/angular.module
If you use angular-mocks it takes care of that for you https://github.com/angular/angular.js/blob/master/src/ngMock/angular-mocks.js#L1785
Can't think of any other reason as to why this problem would occur.
I am trying to test an Angular directive that has an external template, but cannot get this to work. This is my first attempt at using Karma. I have Googled for a solution, and tried various changes to the karma.conf.js file, but still keep getting this:
Error: [$injector:modulerr] Failed to instantiate module app/inventory/itemDetails.html due to:
Error: [$injector:nomod] Module 'app/inventory/itemDetails.html' is not available! You either misspelled the module name or forgot to load it.
Folder structure:
app
inventory
itemDetails.html
itemDetailsDirective.js (templateUrl: "app/inventory/itemDetails.html")
UnitTest
karma.conf.js
specs
inventorySpec.js
karma.conf.js
// list of files / patterns to load in the browser
files: [
'../scripts/jquery.min.js',
'scripts/angular.js',
'scripts/angular-mocks.js',
'../app/*.js',
'../app/**/*.js',
'../app/inventory/itemDetails.html',
'specs/*.js'
],
preprocessors: {
'../app/inventory/itemDetails.html':['ng-html2js'] // Is this supposed to be the path relative to the karma.conf.js file?
},
ngHtml2JsPreprocessor: {
stripPrefix: '../',
},
itemDetailsDirective.js
templateUrl: "app/inventory/itemDetails.html",
inventorySpec.js (most stuff commented out for debug purposes)
describe("itemDetailsDirective", function () {
var element, $scope;
beforeEach(module("app/inventory/itemDetails.html"));
beforeEach(inject(function ($compile, $rootScope) {
console.log("itemDetailsDirective");
//element = angular.element('<item-details></item-details>');
//$scope = $rootScope.$new();
//$compile(element)($scope);
//$scope.$digest();
}));
it('should display', function () {
// var isolatedScope = element.isolateScope();
// //expect(isolatedScope.condition).toEqual("Fair");
});
});
So I have a UnitTest folder (VS 2013 project) parallel to the app folder. The paths under "files" in karma.conf.js are correct - all "non-templateUrl" tests work fine.
Help would be great, while I still have some hair left!
I got this working, thanks to this article that I just came across: http://willemmulder.net/post/63827986070/unit-testing-angular-modules-and-directives-with
The key is that the paths are relative to the DISK root!
To illustrate, I changed the karma.conf.js file to use cahceIdFromPath:
ngHtml2JsPreprocessor: {
cacheIdFromPath : function(filepath) {
console.log("karma, cacheIdFromPath " + filepath);
var templateFile = filepath.substr(filepath.indexOf("/app/") + 1 );
console.log("karma, templateFile: " + templateFile);
return templateFile;
},
},
Output:
karma, cacheIdFromPath C:/Dev/BcCom/app/inventory/itemDetails.html
karma, templateFile: app/inventory/itemDetails.html
With the above, it now works as it should!
I am not sure what do you want to test but you are trying to use an html as module which I think is not correct and probably you have to use $httpbackendto mock the GET request. I made a dummy example:
'use strict';
describe('TestMyDirective', function() {
var $compile, $http, $httpBackend, $rootScope, $scope, template;
beforeEach(module('myApp'));
beforeEach(inject(function($injector) {
$rootScope = $injector.get('$rootScope');
$compile = $injector.get('$compile');
// Inject $httpBackend service
$httpBackend = $injector.get('$httpBackend');
// Mock request to your html
$httpBackend.whenGET('my.html').respond("GotIT");
$scope = $rootScope.$new();
template = $compile("<my-directive> </my-directive>")($scope);
$scope.$digest();
}));
/*
*
* Tests
*
*/
describe('Test something', function() {
it('should test something', function() {
// You can test that your directive add something on the $scope
});
});
});
I am using an AngularJS service in my app. I've learned that the service I've created is unreliable. For that reason, I want to build some unit tests around it. While the service works fine in my AngularJS app, I'm having problems getting it to work with Jasmine. Its like I can't load any modules. I've strip down the code to the bare bones. I do not understand what I'm doing wrong. My code looks like this:
myService.js
'use strict';
angular.module('customModule', [])
.factory('$serviceName', [function () {
return {
isAvailable: function () {
return true;
}
};
}]
);
myService.spec.js
describe('customModule', function() {
beforeEach(function() {
console.log('loading module...');
module('customModule');
});
describe('$serviceName', function () {
var myService = null;
beforeEach(inject(function ($serviceName) {
console.log('loading service...');
myService = $serviceName;
}));
it('should work', function () {
console.log('testing');
var isAvailable = myService.isAvailable();
console.log(isAvailable);
expect(1 + 2).toEqual(3);
});
});
})
gruntfile.js
'use strict';
module.exports = function (grunt) {
grunt.initConfig({
jasmine: {
unitTests: {
src: 'test/*.js',
}
}
});
require('load-grunt-tasks')(grunt);
grunt.registerTask('default', ['jasmine:unitTests']);
};
My jasmine tests are running. However, its like myService.js isn't being loaded. I'm not sure how to do that. I'm also not sure how to get 'angular' (used in myService.js) in the tests either.
Thank you for your help.
Do you load you Angular application in myService.spec.js? Otherwise $serviceName is not available for dependency injection.
You should add something like this in your test file:
beforeEach(module('angularApp'));
(of course replace angularApp with the name you are using)
And it is also a good idea to use another beforeEach block to handle the dependency injection so you can use it in all tests in myService.spec.js. In total you should have something like this:
describe('serviceName', function () {
beforeEach(module('angularApp'));
var iMyService
// Initialize the service
beforeEach(inject(function ($serviceName) {
iMyService = $serviceName;
}));
it('should check if available', function () {
var isAvailable = iMyService.isAvailable();
console.log(isAvailable);
expect(1 + 2).toEqual(3);
});
});
I'm trying to mock out this 'usermanager' provider in my controller test and I always end up getting this error
TypeError: Attempted to assign to readonly property.
at workFn (c:{...}/bower_components/angular-mocks/angular-mocks.js:2105)
Here is my controller:
angular.module('sasApp')
.controller('RegisterCtrl', function ($scope, usermanager) {
// variables
$scope.captcha = {};
// Get captcha from server
usermanager.getCaptcha().then(function (captcha) {
$scope.captcha = captcha;
}, function (reason) {
console.log('Failed: ' + reason);
});
});
And here is my test:
describe('Controller: RegisterCtrl', function () {
// load the controller's module
beforeEach(module('sasApp'));
var RegisterCtrl,
scope,
mockUsermanager;
mockUsermanager = {
getCaptcha: function () {
return {
test: 1
};
}
};
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
RegisterCtrl = $controller('RegisterCtrl', {
$scope: scope,
usermanager: mockUsermanager
});
}));
describe('Initialization', function () {
it('should have \'captcha\' object defined', function () {
expect(scope.captcha).toBeDefined();
});
});
});
The thing is, I didn't get this error until I tried setting up the mocking of the 'usermanager' provider. If I remove the line 'usermanager: mockUsermanager' in the beforeEach statement, then it all runs well.
Can you spot what is wrong ? Thanks in advance!
It might not be the best answer but I've made a Plnkr that does not reproduce the issue here:
http://plnkr.co/GgUeKnsvxEHmD3Wcyocx?p=preview
A quick note:
usermanager.getCaptcha() has to return a promise otherwise the next part of test would blow up when it tries calling .then. So I've just put in a bogus one in.
Let me know if you spot any differences (I don't know what angular version you're on for example). And I can see about updating