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
});
});
});
Related
I want to test an angular directive with karma and jasmine. The directive uses an external template named complete.html.
I try to get the html template with the following code:
describe('directive', function () {
beforeEach(module('sampleapp'));
it('test',
function(){
inject(function ($rootScope, $compile) {
var $scope = $rootScope.$new();
var element = $compile(angular.element('<auto-complete></auto-complete>'))($scope);
$scope.$digest();
var textarea = element.find('textarea');
expect(textarea.length).toBe(1);
});
}
); });
I then receive this error:
Error: Unexpected request: GET test/complete.html
I searched for a solution for my problem and found this post:
Unit Testing AngularJS directive with templateUrl
I followed the instructions for the karma-solution.
My karma.conf.js looks like the following:
plugins : [
'karma-ng-html2js-preprocessor',
'karma-chrome-launcher',
'karma-jasmine'
],
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
'../complete.html': ['ng-html2js']
},
ngHtml2JsPreprocessor: {
moduleName: "complete.html"
},
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['jasmine'],
// list of files / patterns to load in the browser
files: [
'../src/main/web/bower_components/jquery/dist/jquery.min.js' ,
'../src/main/web/bower_components/angular/angular.min.js' ,
'../src/main/web/bower_components/angular-route/angular-route.js',
'../src/main/web/bower_components/angular-mocks/angular-mocks.js',
'../complete.html',
'../src/main/web/scripts/application.js',
'../src/main/web/scripts/main.js',
'../src/main/web/scripts/service.js',
'../src/main/web/scripts/plz.json',
'tests.js'
],
(I cropped out the unnecessary parts obviously)
Then as instructed i try to load the generated module complete.html in my test file:
describe('directive', function () {
beforeEach(module('sampleapp'));
beforeEach(module('complete.html'));
it('test',
function(){
inject(function ($rootScope, $compile) {
var $scope = $rootScope.$new();
var element = $compile(angular.element('<auto-complete></auto-complete>'))($scope);
$scope.$digest();
var textarea = element.find('textarea');
expect(textarea.length).toBe(1);
});
}
);});
But i still get the error Error: Unexpected request: GET test/complete.html
I feel like I miss to do an important part but cant find a complete solution for the situation I am in. How do i correctly use the generated module so I dont get the error?
I would be very thankful for some help.
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');
I'm going through the AngularJS PhoneCat tutorial and while the application seems to behave correctly when I click through it manually, one of the unit tests are failing for step 8.
Using Karma, I'm getting the following console output:
Chrome 42.0.2311 (Windows 7) PhoneCat controllers PhoneDetailCtrl
should fetch phone detail FAILED Error: [$injector:unpr] Unknown
provider: $routeParamsProvider <- $routeParams
The relevant part of the unit test file (controllersSpec.js) looks like this:
describe("PhoneCat controllers", function () {
describe('PhoneDetailCtrl', function () {
var scope, $httpBackend, ctrl;
beforeEach(inject(function (_$httpBackend_, $rootScope, $routeParams, $controller) {
$httpBackend = _$httpBackend_;
$httpBackend.expectGET('phones/xyz').respond({ name: 'phone xyz' });
$routeParams.phoneId = 'xyz';
scope = $rootScope.$new();
ctrl = $controller('PhoneDetailCtrl', { $scope: scope });
}));
});
});
The problem seems to be related to the declaration of the function parameter of the call to inject(). If I take $routeParams out then the script will execute.
From reading some related StackOverflow questions it seems I might be missing a dependency somewhere. The relevant parts of the karma.conf.js file look like this:
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine'],
files: [
'../lib/angular/angular.js',
'../lib/angular/angular-mocks.js',
'../lib/angular/angular-route.js',
'../app/*.js',
'unit/*.js'
],
browsers: ['Chrome'],
singleRun: false
});
};
My app.js looks like this:
var phonecatApp = angular.module("phonecatApp", [
"ngRoute",
"phonecatControllers"]);
phonecatApp.config([
"$routeProvider", function($routeProvider) {
$routeProvider
.when("/phones", {
templateUrl: "Scripts/app/partials/phone-list.html",
controller: "PhoneListCtrl"
})
.when("/phones/:phoneId", {
templateUrl: "Scripts/app/partials/phone-detail.html",
controller: "PhoneDetailCtrl"
})
.otherwise({
redirectTo: "/phones"
});
}
]);
And controllers.js looks like this:
var phonecatControllers = angular.module("phonecatControllers", []);
phonecatControllers.controller("PhoneDetailCtrl", ["$scope", "$routeParams", "$http", function ($scope, $routeParams, $http) {
$http.get("api/Phones/" + $routeParams.phoneId).success(function (data) {
$scope.phone = data;
});
}]);
As mentioned at the top, the app seems to work fine when I click through it in the browser so I'm quite confused as to where things are breaking down for the unit tests.
As is often the case with these kind of errors, the fix is pretty obvious when you see it! Basically there are two functions in controllersSpec.js, one for a list of phones and one for an individual phone's details. The phone list tests were working fine because they had
beforeEach(module("phonecatApp")); before the call to beforeEach(inject(function ());
The phone detail tests on the other hand were missing this line. Once I moved the line to the outer function the tests passed as expected.
The working controllersSpec.js looks like this:
describe("PhoneCat controllers", function () {
// This is the line that was moved.
// It existed for PhoneListCtrl but not PhoneDetailCtrl.
beforeEach(module("phonecatApp"));
describe("PhoneListCtrl", function() {
var scope, ctrl, $httpBackend;
beforeEach(module("phonecatApp"));
beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) {
// Setup
}));
// Various tests
});
describe('PhoneDetailCtrl', function () {
var scope, $httpBackend, ctrl;
beforeEach(inject(function (_$httpBackend_, $rootScope, $routeParams, $controller) {
// Setup
}));
// Various tests
});
});
I'm trying to test my application with Karma but I get following Errors:
minErr/<#/home/usr/webui-ng/src/client/app/bower_components/angular/angular.js:78:5
loadModules/<#/home/usr/webui-ng/src/client/app/bower_components/angular/angular.js:3859:1
forEach#/home/usr/webui-ng/src/client/app/bower_components/angular/angular.js:325:7
loadModules#/home/usr/webui-ng/src/client/app/bower_components/angular/angular.js:3824:5
createInjector#/home/usr/webui-ng/src/client/app/bower_components/angular/angular.js:3764:3
workFn#/home/usr/webui-ng/src/client/app/bower_components/angular-mocks/angular-mocks.js:2150:9
These are my files:
hello.js
angular.module('myApp', [])
.controller('MainController', function($scope) {
$scope.name = "Ari";
$scope.sayHello = function() {
$scope.greeting = "Hello " + $scope.name;
}
})
hello.js - test file :
describe('Unit: MainController', function() {
// Load the module with MainController
beforeEach(module('myApp'));
var ctrl, scope;
// inject the $controller and $rootScope services
// in the beforeEach block
beforeEach(inject(function($controller, $rootScope) {
// Create a new scope that's a child of the $rootScope
scope = $rootScope.$new();
// Create the controller
ctrl = $controller('MainController', {
$scope: scope
});
}));
it('should create $scope.greeting when calling sayHello',
function() {
expect(scope.greeting).toBeUndefined();
scope.sayHello();
expect(scope.greeting).toEqual("Hello Ari");
});
});
As you can see, I've already loaded the module, as the solution was in Controller undeclared in Jasmine test.
What else could it be? I haven't found much about these errors on the web. I would be very happy to finally find an answer for that.
As runTarm mentioned, the path in the karma.conf.js was not set to the actual path of the script file (hello.js).
I am unit testing an Angular directive and would like to mock or stub in some way the instantiation of the named controller in the unit test.
So first I suppose on to some code...
'use strict';
angular.module('App.Directives.BreadCrumbs', [])
.directive('kxBreadcrumbs', function () {
return {
restrict: 'E',
controller: 'BreadCrumbsController',
template:
'<!-- Breadcrumbs Directive HTML -->' +
'<ol class="breadcrumb">' +
' <li ng-repeat="crumb in crumbPath">' +
' <a ng-class="{true: \'disable\', false: \'\'}[crumb.last]" href="{{crumb.href}}" ng-click="updateCrumb(crumb.name)">{{crumb.name}}</a>' +
' </li>' +
'</ol>' +
'<!-- End of Breadcrumbs Driective HTML -->'
};
});
This is one sample directive that I would unit test, the important thing to take away from this is the named controller.
So in my unit test
'use strict';
describe('Directives: Breadcrumbs', function () {
var//iable declarations
elm,
scope,
$rootScope
;
beforeEach(function () {
module('App.Directives.BreadCrumbs');
module('App.Controllers.BreadCrumbs');
module('App.Constants'); // <--- Comes from the controller dependancy
});
beforeEach(inject(function (_$rootScope_, $compile) {
$rootScope = _$rootScope_;
scope = $rootScope.$new();
elm = angular.element('<kx-breadcrumbs></kx-breadcrumbs>');
$compile(elm)(scope);
scope.$apply();
}));
it('Should create the breadcrumbs template', function () {
scope.crumbPath = [{name: 'home', href: '/'},{name: 'coffee', href: '/coffee'},{name: 'milk', href: '/milk'}];
scope.$apply();
var listItem = $(elm).find('li');
expect(listItem.length).toBe(3);
expect($(listItem).text()).toContain('home');
expect($(listItem).text()).toContain('coffee');
expect($(listItem).text()).toContain('milk');
});
});
You can see the inclusion of the 3 modules - the directive, the controller and the third one the constants. This is referenced by the controller as a dependancy so in order to pull this into the unit test I need to pull in the dependancy or in much worse cases the dependancies from the controller. But as I am not unit testing the functionality of the controller in the directive unit test, this seem redundant and bloating of code through inclusion of modules. Ideally I would like to only include the module that I am unit testing.
module('App.Directives.BreadCrumbs');
and not (modules added to exemplify my point more)
module('App.Directives.BreadCrumbs');
module('App.Controllers.BreadCrumbs');
module('App.Constants'); // <--- Comes from the controller dependancy
module('App.Service.SomeService'); // <--- Comes from the controller dependancy
module('App.Service.SomeOtherService'); // <--- Comes from the SomeService dependancy
When we unit test controllers we can mock services that are passed in either completely or by using jasmine spies. Can we accomplish the same sorta thing in unit test of directives so I don't have to follow the dependancy trail?
You can create mocks in module configuration block by using $controllerProvider.register() for controllers, $provide.provider(), $provide.factory(), $provide.service() and $provide.value() for providers, factories and services:
JavaScript
beforeEach(function () {
module('App.Directives.BreadCrumbs', function($provide, $controllerProvider) {
$controllerProvider.register('BreadCrumbsController', function($scope) {
// Controller Mock
});
$provide.factory('someService', function() {
// Service/Factory Mock
return {
doSomething: function() {}
}
});
});
});
Once you do so, Angular will inject your mock BreadCrumbsController controller into kxBreadcrumbs directive. This way you don't need to include real controller and it's dependencies into unit test.
For more information see Angular's official documentation on:
$provide
$controllerProvider