I'm actually stuck since few hours, my boss wants me to proceed to unit testing on the functionalities i coded last week, when I do my karma start karma.conf.js it gives me the following issue :
"message": "An error was thrown in afterAll\nUncaught ReferenceError: angular is not defined",
my files structure :
webapp
-www
-services
api.service.js
-test
test.test.js
karma.conf.js
essential of karma.conf.js :
module.exports = function(config) {
config.set({
files: [
'www/js/services/api.service.js',
'test/**/*.test.js'
],
})
}
I think Karma doesn't find my api.service.js but i don't know why, the angularjs code actually work in the webapp.
content of my test.test.js :
(function() {
'use strict';
describe('apiService', function() {
it('should return an array of object', function() {
var artist = typeof getArtist(118680); // MGMT Artist
console.log(artist);
expect(artist).toEqual('array');
});
});
})();
getArtist is the function I need to test situated in api.service.js.
Thank you,
Paul.
Karma needs the angular file to test angularJS code. You need to include it in the files array in the karma configuration.
module.exports = function(config) {
config.set({
files: [
'path-to-angular.js',
'www/js/services/api.service.js',
'test/**/*.test.js'
],
})
}
I create a very small project to test the unit test in AngularJS. The test work until I try to include the dependence restangular. Why it generates conflicts?. Files:
karma.conf.js
files: [
'lib/angular.js',
'lib/angular-route.js',
'lib/angular-mocks.js',
'lib/angular-cookies.js',
'lib/angular-md5.js',
'lib/restangular.js',
'app.js',
'tests/app.spec.js'
]
app.js
var phonecatApp = angular.module('phonecatApp', [
'ngRoute',
'ngCookies',
'angular-md5',
'restangular' //This generate ERROR!
]);
app.spec.js
describe('PhoneListController', function() {
beforeEach(module('phonecatApp'));
beforeEach(module('ngRoute'));
beforeEach(module('angular-md5'));
beforeEach(module('restangular')); //This generate ERROR!
it('should...', inject(function($controller) {
var scope = {};
var ctrl = $controller('PhoneListController', {$scope: scope});
expect(scope.phones.length).toBe(3);
}));
When you trying to load restangular. Then you might be getting error like
Faild to instantiate module restangular due to (_'underscore.js') is undefined. The '_'(undescore) javascript utility library that Restangular uses and depending on it. Include (_'underscore.js') library before the angular js in your template.
Here is your working jsfiddle code please take a look http://jsfiddle.net/chhitij92/x67u4Ldu/ In external resource I Included '_'(undescore) cdn after this its working.
I have a problem to run my tests in Webstorm 8, here is my conf file :
Here is my AngularJS controller :
angular.module('monApp').controller('DashboardCtrl', [
'$scope', function ($scope) {
'use strict';
$scope.foo = 'bar';
}
]);
And my test file :
describe('Controller: DashboardCtrl', function () {
'use strict';
var DashboardCtrl,
scope;
beforeEach(module('monApp'));
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
DashboardCtrl = $controller('DashboardCtrl', {
$scope : scope
});
}));
it('dashboard should be defined', function () {
expect(scope.foo).toBeDefined();
expect(scope.foo).toBe('bar');
});
});
These are very simple, I followed steps of many tutorials, first installed node, then install karma with npm install, and when i run karma with karma start my.conf.js the test pass but it shows Empty test suite.
I precise that in my project, I just have 5 files :
angular.js
angular-mocks.js
the controller
test file
conf file
In the config file, i have added these 4 files with :
// list of files / patterns to load in the browser
files: [
'angular.js',
'angular-mocks.js',
'*.js'
],
When I try to throw the test by right-click I get an error telling : describe is not defined.
Thanks.
I tried to restart from scratch and it appears that my instantiation of my app module wasn't fine, this is better :
var app = angular.module('monApp', []);
app.controller('DashboardCtrl', ['$scope', function ($scope) { ....
And the first problem "empty test suite" was due to wrong basePath in the config file. Anyway, thanks for your answers !
Your my.conf.js must contain :
frameworks: ['jasmine']
And Karma need the bridge to Jasmine framework to be installed :
npm install karma-jasmine --save-dev
By default, karma loads all installed plugins which are starting with "karma-".
I'm trying for the first time to use AngularJS in conjunction with RequireJS using this guide as a basis. As far I can tell after a lot of debugging I'm loading all my modules in the correct order, but when the application runs Angular throws an Error / Exception with the following message:
Argument 'fn' is not a function, got string from myApp
I've seen this message before due to syntax errors, so even though I've looked trough the code multiple times I won't rule out the possibility of a simple syntax error. Not making a Fiddle just yet in case it is something as simple as a syntax error, but I'll of course do so if requested.
Update: I just noticed when setting ng-app="myApp" in the <html> tag I also get an additional error,
No module: myApp
Update II: Okay, it turns out it indeed was an syntax error in the only file not included below. I am though still left with the problem from update I.
RequireJS bootstrap
'use strict';
define([
'require',
'angular',
'app/myApp/app',
'app/myApp/routes'
], function(require, ng) {
require(['domReady'], function(domReady) {
ng.bootstrap(domReady, ['myApp']);
});
});
app.js
'use strict';
define([
'angular',
'./controllers/index'
], function(ng) {
return ng.module('myApp', [
'myApp.controllers'
]);
}
);
controllers/index
'use strict';
define([
'./front-page-ctrl'
], function() {
});
controllers/module
'use strict';
define(['angular'], function (ng) {
return ng.module('myApp.controllers', []);
});
controllers/front-page-ctrl
'use strict';
define(['./module'], function(controllers) {
controllers.
controller('FrontPageCtrl', ['$scope',
function($scope) {
console.log('I\'m alive!');
}
]);
});
Delete ng-app="myApp" from your html.
Because it has bootstrapped manually
ng.bootstrap(domReady, ['myApp']);
RequireJS docs on Dom ready state:
Since DOM ready is a common application need, ideally the nested
functions in the API above could be avoided. The domReady module also
implements the Loader Plugin API, so you can use the loader plugin
syntax (notice the ! in the domReady dependency) to force the
require() callback function to wait for the DOM to be ready before
executing. domReady will return the current document when used as a
loader plugin:
So, when you require 'domReady' the result is a function:
function domReady(callback) {
if (isPageLoaded) {
callback(doc);
} else {
readyCalls.push(callback);
}
return domReady;
}
But when you append the domReady string with ! sign the result will be the actual document element:
'use strict';
define([
'require',
'angular',
'app/myApp/app',
'app/myApp/routes'
], function(require, ng) {
require(['domReady!'], function(domReady) {
// domReady is now a document element
ng.bootstrap(domReady, ['myApp']);
});
});
Using AngularJS.
Have a directive.
Directive defines templateUrl.
Directive needs unit testing.
Currently unit testing with Jasmine.
This recommends code like:
describe('module: my.module', function () {
beforeEach(module('my.module'));
describe('my-directive directive', function () {
var scope, $compile;
beforeEach(inject(function (_$rootScope_, _$compile_, $injector) {
scope = _$rootScope_;
$compile = _$compile_;
$httpBackend = $injector.get('$httpBackend');
$httpBackend.whenGET('path/to/template.html').passThrough();
}));
describe('test', function () {
var element;
beforeEach(function () {
element = $compile(
'<my-directive></my-directive>')(scope);
angular.element(document.body).append(element);
});
afterEach(function () {
element.remove();
});
it('test', function () {
expect(element.html()).toBe('asdf');
});
});
});
});
Running code in Jasmine.
Getting error:
TypeError: Object #<Object> has no method 'passThrough'
templateUrl needs loading as-is
Cannot use respond
May be related to ngMock use rather than ngMockE2E use.
You're correct that it's related to ngMock. The ngMock module is automatically loaded for every Angular test, and it initializes the mock $httpBackend to handle any use of the $http service, which includes template fetching. The template system tries to load the template through $http and it becomes an "unexpected request" to the mock.
What you need a way to pre-load the templates into the $templateCache so that they're already available when Angular asks for them, without using $http.
The Preferred Solution: Karma
If you're using Karma to run your tests (and you should be), you can configure it to load the templates for you with the ng-html2js preprocessor. Ng-html2js reads the HTML files you specify and converts them into an Angular module that pre-loads the $templateCache.
Step 1: Enable and configure the preprocessor in your karma.conf.js
// karma.conf.js
preprocessors: {
"path/to/templates/**/*.html": ["ng-html2js"]
},
ngHtml2JsPreprocessor: {
// If your build process changes the path to your templates,
// use stripPrefix and prependPrefix to adjust it.
stripPrefix: "source/path/to/templates/.*/",
prependPrefix: "web/path/to/templates/",
// the name of the Angular module to create
moduleName: "my.templates"
},
If you are using Yeoman to scaffold your app this config will work
plugins: [
'karma-phantomjs-launcher',
'karma-jasmine',
'karma-ng-html2js-preprocessor'
],
preprocessors: {
'app/views/*.html': ['ng-html2js']
},
ngHtml2JsPreprocessor: {
stripPrefix: 'app/',
moduleName: 'my.templates'
},
Step 2: Use the module in your tests
// my-test.js
beforeEach(module("my.templates")); // load new module containing templates
For a complete example, look at this canonical example from Angular test guru Vojta Jina. It includes an entire setup: karma config, templates, and tests.
A Non-Karma Solution
If you do not use Karma for whatever reason (I had an inflexible build process in legacy app) and are just testing in a browser, I have found that you can get around ngMock's takeover of $httpBackend by using a raw XHR to fetch the template for real and insert it into the $templateCache. This solution is much less flexible, but it gets the job done for now.
// my-test.js
// Make template available to unit tests without Karma
//
// Disclaimer: Not using Karma may result in bad karma.
beforeEach(inject(function($templateCache) {
var directiveTemplate = null;
var req = new XMLHttpRequest();
req.onload = function() {
directiveTemplate = this.responseText;
};
// Note that the relative path may be different from your unit test HTML file.
// Using `false` as the third parameter to open() makes the operation synchronous.
// Gentle reminder that boolean parameters are not the best API choice.
req.open("get", "../../partials/directiveTemplate.html", false);
req.send();
$templateCache.put("partials/directiveTemplate.html", directiveTemplate);
}));
Seriously, though. Use Karma. It takes a little work to set up, but it lets you run all your tests, in multiple browsers at once, from the command line. So you can have it as part of your continuous integration system, and/or you can make it a shortcut key from your editor. Much better than alt-tab-refresh-ad-infinitum.
What I ended up doing was getting the template cache and putting the view in there. I don't have control over not using ngMock, it turns out:
beforeEach(inject(function(_$rootScope_, _$compile_, $templateCache) {
$scope = _$rootScope_;
$compile = _$compile_;
$templateCache.put('path/to/template.html', '<div>Here goes the template</div>');
}));
This initial problem can be solved by adding this:
beforeEach(angular.mock.module('ngMockE2E'));
That's because it tries to find $httpBackend in ngMock module by default and it's not full.
The solution I reached needs jasmine-jquery.js and a proxy server.
I followed these steps:
In karma.conf:
add jasmine-jquery.js to your files
files = [
JASMINE,
JASMINE_ADAPTER,
...,
jasmine-jquery-1.3.1,
...
]
add a proxy server that will server your fixtures
proxies = {
'/' : 'http://localhost:3502/'
};
In your spec
describe('MySpec', function() {
var $scope, template;
jasmine.getFixtures().fixturesPath = 'public/partials/'; //custom path so you can serve the real template you use on the app
beforeEach(function() {
template = angular.element('');
module('project');
inject(function($injector, $controller, $rootScope, $compile, $templateCache) {
$templateCache.put('partials/resources-list.html', jasmine.getFixtures().getFixtureHtml_('resources-list.html')); //loadFixture function doesn't return a string
$scope = $rootScope.$new();
$compile(template)($scope);
$scope.$apply();
})
});
});
Run a server on your app's root directory
python -m SimpleHTTPServer 3502
Run karma.
It took my a while to figure this out, having to search many posts, I think the documentation about this should be clearer, as it is such an important issue.
My solution:
test/karma-utils.js:
function httpGetSync(filePath) {
var xhr = new XMLHttpRequest();
xhr.open("GET", "/base/app/" + filePath, false);
xhr.send();
return xhr.responseText;
}
function preloadTemplate(path) {
return inject(function ($templateCache) {
var response = httpGetSync(path);
$templateCache.put(path, response);
});
}
karma.config.js:
files: [
//(...)
'test/karma-utils.js',
'test/mock/**/*.js',
'test/spec/**/*.js'
],
the test:
'use strict';
describe('Directive: gowiliEvent', function () {
// load the directive's module
beforeEach(module('frontendSrcApp'));
var element,
scope;
beforeEach(preloadTemplate('views/directives/event.html'));
beforeEach(inject(function ($rootScope) {
scope = $rootScope.$new();
}));
it('should exist', inject(function ($compile) {
element = angular.element('<event></-event>');
element = $compile(element)(scope);
scope.$digest();
expect(element.html()).toContain('div');
}));
});
If you are using Grunt, you can use grunt-angular-templates. It loads your templates in the templateCache and it's tranparent to your specs configuration.
My sample config:
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
ngtemplates: {
myapp: {
options: {
base: 'public/partials',
prepend: 'partials/',
module: 'project'
},
src: 'public/partials/*.html',
dest: 'spec/javascripts/angular/helpers/templates.js'
}
},
watch: {
templates: {
files: ['public/partials/*.html'],
tasks: ['ngtemplates']
}
}
});
grunt.loadNpmTasks('grunt-angular-templates');
grunt.loadNpmTasks('grunt-contrib-watch');
};
I solved the same problem in a slightly different way than the chosen solution.
First, I installed and configured the ng-html2js plugin for
karma. In the karma.conf.js file :
preprocessors: {
'path/to/templates/**/*.html': 'ng-html2js'
},
ngHtml2JsPreprocessor: {
// you might need to strip the main directory prefix in the URL request
stripPrefix: 'path/'
}
Then I loaded the module created in the beforeEach.
In your Spec.js file :
beforeEach(module('myApp', 'to/templates/myTemplate.html'));
Then I used $templateCache.get to store it into a variable.
In your Spec.js file :
var element,
$scope,
template;
beforeEach(inject(function($rootScope, $compile, $templateCache) {
$scope = $rootScope.$new();
element = $compile('<div my-directive></div>')($scope);
template = $templateCache.get('to/templates/myTemplate.html');
$scope.$digest();
}));
Finally, I tested it this way.
In your Spec.js file:
describe('element', function() {
it('should contain the template', function() {
expect(element.html()).toMatch(template);
});
});
To load the template html dynamically into $templateCache you could just use html2js karma pre-processor, as explained here
this boils down to adding templates '.html' to your files in the conf.js file
as well
preprocessors = {
'.html': 'html2js'
};
and use
beforeEach(module('..'));
beforeEach(module('...html', '...html'));
into your js testing file
if you're using Karma, consider using karma-ng-html2js-preprocessor to pre-compile your external HTML templates and avoid having Angular try to HTTP GET them during test execution. I struggled with this for a couple of ours - in my case templateUrl's partial paths resolved during normal app execution but not during tests - due to differences in app vs. test dir structures.
If you are using the jasmine-maven-plugin together with RequireJS you can use the text plugin to load the template content into a variable and then put it in the template cache.
define(['angular', 'text!path/to/template.html', 'angular-route', 'angular-mocks'], function(ng, directiveTemplate) {
"use strict";
describe('Directive TestSuite', function () {
beforeEach(inject(function( $templateCache) {
$templateCache.put("path/to/template.html", directiveTemplate);
}));
});
});
If you use requirejs in your tests then you can use the 'text' plugin to pull in the html template and put it in the $templateCache.
require(["text!template.html", "module-file"], function (templateHtml){
describe("Thing", function () {
var element, scope;
beforeEach(module('module'));
beforeEach(inject(function($templateCache, $rootScope, $compile){
// VOILA!
$templateCache.put('/path/to/the/template.html', templateHtml);
element = angular.element('<my-thing></my-thing>');
scope = $rootScope;
$compile(element)(scope);
scope.$digest();
}));
});
});
I resolve this issue with compiling all templates to templatecache.
I'm using gulp, you can find similar solution for grunt too.
My templateUrls in directives, modals looks like
`templateUrl: '/templates/directives/sidebar/tree.html'`
Add a new npm package in my package.json
"gulp-angular-templatecache": "1.*"
In gulp file add templatecache and a new task:
var templateCache = require('gulp-angular-templatecache');
...
...
gulp.task('compileTemplates', function () {
gulp.src([
'./app/templates/**/*.html'
]).pipe(templateCache('templates.js', {
transformUrl: function (url) {
return '/templates/' + url;
}
}))
.pipe(gulp.dest('wwwroot/assets/js'));
});
Add all js files in index.html
<script src="/assets/js/lib.js"></script>
<script src="/assets/js/app.js"></script>
<script src="/assets/js/templates.js"></script>
Enjoy!