I'm trying to unit test all the directives using karma and jasmine. When I try to load a directive, which has a template called header.html, I get the following error: Error: Unexpected request: GET header.html No more request expected
http://plnkr.co/edit/YTUFXCMdK5JFEwMzzXaR?p=preview
Update:
I have the following config in karma.conf.js:
files: [
'test/client/specs/main.js',
// 'WebContent/modules/common/header/**/*.html',
{pattern: 'WebContent/libs/**/*.js', included: false},
{pattern: 'WebContent/modules/**/*.js', included: false},
{pattern: 'WebContent/modules/common/header/tmpl/*.html', included: false},
{pattern: 'test/client/specs/**/*spec.js', included: false}
],
// generate js files from html templates
preprocessors: {
'WebContent/modules/common/header/**/*.html': ['ng-html2js']
},
ngHtml2JsPreprocessor: {
'moduleName': 'Templates',
cacheIdFromPath: function(filepath) {
return filepath.match(/\/WebContent\/modules\/common\/header\/.*\.html/);
}
},
I'm trying to load it by:
beforeEach(function() {
module("Templates");
});
Now i get the following errors:
Error: [$injector:modulerr] Failed to instantiate module Templates due to:
Error: [$injector:nomod] Module 'Templates' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.
http://errors.angularjs.org/1.2.12/$injector/nomod?p0=Templates
I solved this in my unit tests by injecting the $templateCache into the test and then putting the html into the cache.
http://plnkr.co/edit/btgYfiiRzacS6MfPFlbv?p=preview
I researched a few different approaches, and we settled on putting the html into the directive.
template: "<div>This is the template</div>"
It makes it much easier to test as you no longer need to update the templateCache in the unit test, which is a pain in the ass and error prone when you have a big piece of html in your directive.
The Karma way is 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
Related
I've set up an Angular project using webpack with help from this blog post which has an associated boilerplate on Github.
The app itself is working fine, but when I run karma it says it can't find the controller it is trying to test.
// karma.config.js
module.exports = config => config.set({
files: ['test.webpack.js'],
preprocessors: {
'test.webpack.js': ['webpack', 'sourcemap']
},
browsers: ['PhantomJS2'],
webpack: require('./webpack.config'),
webpackMiddleware: {
noInfo: 'errors-only',
},
});
// test.webpack.js
import 'angular';
import 'angular-mocks/angular-mocks';
const testContext = require.context('./', true, /\.spec\.js$/);
testContext.keys().forEach(testContext);
I only have one spec file right now for MainCtrl.spec.js. Karma will run it, but I get the error:
Argument 'MainCtrl' is not a function, got undefined
When I actually run the app, MainCtrl seems to load just fine, so I'm not sure why Karma can't get it.
I've also tried changing \.spec\.js to just \.js but this causes way more errors.
I was able to solve this by rethinking the workflow a bit. Two main issues are that module does not get globally exported from ngMocks (there is a conflict with the node import system). The other is that the tests also have to load all of the module files first which is pretty typical for Karma.
// karma.config.js
files: ['app/index.js', 'test.webpack.js']
preprocessors: {
'app/index.js': ['webpack'],
'test.webpack.js': ['webpack'],
}
// test.config.js
/* Angular is already imported by app/index.js */
import 'angular-mocks/angular-mocks';
/* MUST use `angular.mock.module`. `module` alone won't work */
beforeEach(angular.mock.module('ngculator'));
const testContext = require.context('.', true, /\.spec\.js$/);
testContext.keys().forEach(testContext);
Trying to test a project using PegJS and requirejs.
I have a couple of source files, implemented as AMD module (define) which loads through the require API. Below the directory structure:
js/
somefile.js
main.js
parser.js
test/
parser.spec.js
I've written a parser.js module to load a PegJS grammar file and use PegJS to create a peg parser:
define(function() {
'use strict';
var PEG = require('pegjs');
var grammarFile = 'grammar.peg'
return {
parse: function(fs, content, debug) {
var grammar = fs.readFileSync(grammarFile, 'utf8').toString();
// Build parser from grammar
var parser = PEG.buildParser(grammar, { trace: debug });
[...]
This works fine with a main.js executed on the command line with node.
Now I want to test my project using karma, jasmine and PhantomJS. I have a karma.conf.js like this:
frameworks: ['jasmine', 'requirejs'],
files: [
{ pattern: './test/**/*.spec.js', included: false },
{ pattern: './js/**/*.js', included: false},
'./test/test-main.js',
],
Also have a require bootstrap file called test-main.js which is configured this way:
'use strict';
var allTestFiles = [];
var TEST_REGEXP = /(spec|test)\.js$/i;
// Get a list of all the test files to include
Object.keys(window.__karma__.files).forEach(function(file) {
console.log(file);
if (TEST_REGEXP.test(file)) {
// Normalize paths to RequireJS module names.
// If you require sub-dependencies of test files to be loaded as-is (requiring file extension)
// then do not normalize the paths
var normalizedTestModule = file.replace(/^\/base\/|\.js$/g, '');
allTestFiles.push(file);
}
});
require.config({
// Karma serves files under /base, which is the basePath from your config file
baseUrl: '/base/js',
// dynamically load all test files
deps: allTestFiles,
// we have to kickoff jasmine, as it is asynchronous
callback: window.__karma__.start
});
Now, when I launch my test (grunt karma), I got this error:
PhantomJS 1.9.8 (Linux 0.0.0) ERROR: Error{message: 'Module name "pegjs" has not been loaded yet for context: _. Use require([])
So I try to include pegjs in the files loaded by Karma this way karma.conf.js:
files: [
{ pattern: 'node_modules/pegjs/lib/**/*.js', included: true },
{ pattern: './test/**/*.spec.js', included: false },
{ pattern: './js/**/*.js', included: false},
'./test/test-main.js'
],
When I do this, I still get an error:
Error: Module name "utils/arrays" has not been loaded yet for context: _. Use require([])
Looking inside pegjs module, there is indeed an arrays.js file:
compiler/
compiler.js
grammar-error.js
parser.js
peg.js
utils/
arrays.js
classes.js
objects.js
So trying to include arrays too:
files: [
{ pattern: 'node_modules/pegjs/lib/utils/arrays.js', included: true },
{ pattern: 'node_modules/pegjs/lib/**/*.js', included: true },
{ pattern: './test/**/*.spec.js', included: false },
{ pattern: './js/**/*.js', included: false},
'./test/test-main.js'
],
I get:
ReferenceError: Can't find variable: module
at /blabla/node_modules/pegjs/lib/utils/arrays.js:108
Because of:
108 module.exports = arrays;
So, intead of loading the npm module, I tried to load the bower module this way:
files: [
{ pattern: 'bower_components/pegjs/peg-0.9.0.js', included: true },
{ pattern: './test/**/*.spec.js', included: false },
{ pattern: './js/**/*.js', included: false},
'./test/test-main.js'
],
And here you go again:
PhantomJS 1.9.8 (Linux 0.0.0) ERROR: Error{message: 'Module name "pegjs" has not been loaded yet for context: _. Use require([])
Also tried not to include pegjs in the karma generated web page:
files: [
{ pattern: 'bower_components/pegjs/peg-0.9.0.js', included: false },
{ pattern: './test/**/*.spec.js', included: false },
{ pattern: './js/**/*.js', included: false},
'./test/test-main.js'
],
But it fails with:
PhantomJS 1.9.8 (Linux 0.0.0) ERROR: 'There is no timestamp for /base/bower_components/pegjs/peg-0.9.0!'
Tried to put the bower_component folder inside the js folder but no luck.
So I don't know were to go from here... Couldn't find anything relevant on Google or here. It seems to be a specific problem to requirejs/pegjs with karma... Any idea is welcome.
UPDATE following dan's answer:
So I switch from a synchronous require to a asynchronous require in parser.js:
define(['../bower_components/pegjs/peg-0.9.0'], function(PEG) {
'use strict';
var grammarFile = 'grammar.peg'
return {
parse: function(fs, content, debug) {
var grammar = fs.readFileSync(grammarFile, 'utf8').toString();
// Build parser from grammar
var parser = PEG.buildParser(grammar, { trace: debug });
[...]
Tried to include the pegjs bower component in karma.conf.js:
{ pattern: 'bower_components/pegjs/peg-0.9.0.js', included: false },
or not include it:
{ pattern: 'bower_components/pegjs/peg-0.9.0.js', included: true },
But always get the same error:
Error: Script error for "/blabla/bower_components/pegjs/peg-0.9.0", needed by: /blabla/js/parser.js
http://requirejs.org/docs/errors.html#scripterror
at /blabla/node_modules/requirejs/require.js:140
Yes the file exists:
$ file /home/aa024149/share/logofrjs/bower_components/pegjs/peg-0.9.0.js
/blabla/bower_components/pegjs/peg-0.9.0.js: ASCII text, with very long lines
UPDATE2: Finally understood and found an acceptable solution.
It sounds like you're loading pegjs via requirejs. If this is the case, pegjs should not be a file that is included. In your karma.conf.js, have you tried the following:
files: [
{ pattern: 'bower_components/pegjs/peg-0.9.0.js', included: false },
{ pattern: './test/**/*.spec.js', included: false },
{ pattern: './js/**/*.js', included: false},
'./test/test-main.js'
],
The value for included indicates whether or not the webpage that the karma server generates should have a script tag for that file or not (See http://karma-runner.github.io/0.13/config/files.html). So your karma.config of:
files: [
{ pattern: 'bower_components/pegjs/peg-0.9.0.js', included: true },
{ pattern: './test/**/*.spec.js', included: false },
{ pattern: './js/**/*.js', included: false},
'./test/test-main.js'
],
will cause karma to generate a webpage with a head tag similar to:
<head>
<script src="/base/bower_components/pegjs/peg-0.9.0.js"></script>
<script src="/base/require.js"></script>
<script src="/base/test/test-main.js"></script>
</head>
In my experience, I've seen a lot of behavior similar to this that was caused by marking my files as included: true. If there is a file which you are trying to load with requirejs, make sure that it is marked as included: false.
I believe that this marks it for a pre-processing job, but I'm not entirely sure why this makes such a difference.
As far as I know, Karma is a testing framework which will run your tests in the browser.
This isn't suitable for testing many node modules.
The browser doesn't have the facility to do this synchronously: var PEG = require('pegjs'). This is why it is asking you to use require([]) which you pass a callback to be executed when pegjs finishes loading.
Using the bower version of pegjs and ensuring it is loaded before require('pegjs') is called may help here. This would ensure that pegjs is already loaded for context _ (the default requirejs context, I presume).
It also can't load files from the file system with fs.readFileSync(grammarFile, 'utf8') so you will have to do it another way. You can ask Karma to host your peg grammar by placing it in the files array and then loading it using the requirejs text plugin.
If the module you are testing is aimed at running on node.js and not in the browser then it may be more suited to using a test framework that does not run the code in the browser, but runs it in node so you have all the node modules available to you. If you are aiming this at the browser, I would rewrite it to more specifically target the browser.
So with the help of the various answers and comments from dan and pieceOpiland I finally came to a way to do what I want.
So first, pegjs, like many javascript libraries comes in two formats: npm modules and bower modules.
Npm modules are used for script made for node and called from the command line. Bower modules are used for script loaded in a browser.
First misunderstanding from my part was that the 'require' would work in node and in the browser indistinctly. This is wrong. It seems the only way to require a module so that it works in the browser is through a asynchronous call like:
require(['module'], function(module) {
...
});
Another misunderstanding was that I could load npm modules in the browser. That some kind of magic would operate for the various npm files to be loaded with my page. That might be possible but only using some special tool like browserify. Without special transformation, only the bower version can be loaded in the browser. Additionally, pegjs bower module is made in a way so that global variables are defined like so:
var PEG = {
...
}
module.exports = PEG;
Basically, the bower module plugs a global variable (actually several global variables) to the top level scope.
So instead of having my client code (the one running in the browser AND in node) loading the module, I actually load the module in either:
main.js through a synchronous require to the npm module like so: var PEG = require('pegjs');
main-browser.js through the global variable which becomes available when you load the bower pegjs script (through a <script> tag)
Both those 'mains' are then injecting the PEG variable to my parser function.
For karma to work, I just need to include the pegjs bower module in the generated page (karma.conf.js extract):
files: [
{ pattern: 'bower_components/pegjs/peg-0.9.0.js', included: true },
{ pattern: './test/**/*.spec.js', included: false },
{ pattern: './js/**/*.js', included: false},
'./test/test-main.js',
],
When i'm concatenating bower_components of angularjs (angular & ui-router) using grunt,
I get a (Error: $injector:modulerr Module Error) Complete error here https://goo.gl/0yz6pm
on the built script.
I DO NOT get this error when i'm using the script source directly from the bower_components
Therefore i think it's an issue with concatenation by grunt.
Below is the grunt script,
grunt.initConfig({
concat:{
options: {
},
dist: {
// the files to concatenate
src: ['client/bower_components/angular/angular.min.js','client/bower_components/angular-ui-router/release/angular-ui-router.min.js'],
// the location of the resulting JS file
dest: 'client/bower_components/../assets/scripts/coreScript.js',
nonull: true
}
}
})
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.registerTask('build',['concat']);
}
I've also tried using a seperator:';' in options but to no help.
What can i do to make concatenation work here?
This setup looks fine, assuming all those script references are correct and you are referencing coreScript.js on your page. The only issue I can see here is your call to .registerTask. Observe your implementation
grunt.registerTask('build','concat']);
However, grunt is expecting a task array
grunt.registerTask('build', ['concat']); // close array with [
Could it be this task is not successfully running because of this issue? I am also not seeing a call to readJSON. Could you be also missing the following in your gruntfile.js declaration?
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
concat: {
// ...
}
});
I am setting up a project with Gulp to run unit tests with Mocha, including Angular tests. I have the basic set up working (indexOf, etc.), however when I include angular-mocks I get this error or a node-module error:
ReferenceError in 'gulp-mocha': "Window is not defined"
I've tried including angular-module-mocks, using gulp-mocha-phantomjs... but the result is the same. (With mocha-phantomjs my error was 'Init timeout'.) I've seen many examples of configurations with Mocha and Angular or Gulp and Karma but have not yet found a solution for Gulp, Mocha and Angular alone.
I'm thinking of something similar to this Karma solution to correctly load angular-mocks by specifying it in a config file and forcing Gulp to load it (Angular testing with Karma: "module is not defined"). However, even if this would work, it seems like gulp-mocha does not support loading a configuration file (mocha.opts - https://github.com/sindresorhus/gulp-mocha/issues/26). I would be happy to hear a more straightforward solution.
I am using angular-mocks 1.2.22 and gulp-mocha 1.1.0.
Code snippets:
var mocha = require('gulp-mocha');
gulp.task('test', function () {
return gulp.src('test/*.js', {read: false})
.pipe(mocha({reporter: 'nyan', timeout: 400}));
});
test/test.js
var assert = require('assert');
var angular_mocks = require('angular-mocks'); //Fails only when this line is present
//tests
What finally worked for me with Gulp/Browserify/Mocha was using Karma and Mocha combined.
Specifically, I used gulp-karma, and defined the configuration at karma.config.js and used a dummy file for gulp.src as others have done:
gulp.task('test', function () {
return gulp.src('./foobar.js').pipe(karma({
configFile:'karma.config.js',
action: 'run'
}))
.on('error', handleErrors);
});
Then I used this karma.config.js file. I needed the npm modules karma-mocha, karma-chai, and karma-bro. (With only the first two, I was getting 'require is not defined'. Then of course I tried including karma-requirejs, but that does not work with Browserify. Then I tried karma-commonjs, which still didn't work. Then I tried karma-browserify, and got a strange error involving bundle() that no one seems to have solved (https://github.com/xdissent/karma-browserify/issues/46). Karma-bro did the trick.)
I also needed to preprocess each file referenced in the tests as well as the tests themselves. (For using phantomJS also include karma-phantomjs-launcher. And I am using the bower version of angular-mocks simply because it is more recent: v1.2.25 compared to 1.2.22 for npm - but the npm version might work.)
module.exports = function(config) {
config.set({
basePath: '',
// frameworks to use
frameworks: ['browserify', 'mocha', 'chai'],
// list of files / patterns to load in the browser
files: [
'node_modules/angular/lib/angular.min.js',
'bower_components/angular-mocks/angular-mocks.js',
'source/javascript/controllers/*.js',
'source/javascript/*.js',
'test/*.js'
],
reporters: ['progress'],
port: 9876,
colors: true,
autoWatch: true,
browsers: ['PhantomJS'],
preprocessors: {
'source/javascript/controllers/*.js': ['browserify'],
'source/javascript/*.js': ['browserify'],
'test/*.js': ['browserify']
}
});
};
And finally this test passes. At the end I needed to make sure the names of my modules and controllers were consistent (capitals etc.) to resolve 'Module not defined' errors. For debugging I replaced node_modules/angular/lib/angular.min.js with node_modules/angular/lib/angular.js in the files.
describe('Angular', function() {
describe('App Controllers', function() {
beforeEach(angular.mock.module('App'));
describe('MessageCtrl', function() {
it('should retrieve the correct amount of messsages', angular.mock.inject(function($controller) {
var scope = {},
ctrl = $controller('MessageCtrl', {$scope:scope});
assert.equal(scope.messages.length, 2);
}));
});
});
});
I do get this: 'WARNING: Tried to load angular more than once.' I can live with it.
When running grunt karma, a test on one of the directive fails when it tries to fetch the template. I am using ng-html2js as a preprocessor. Here is some of my karma.conf.js
plugins: ['karma-chrome-launcher',
'karma-jasmine',
'ng-html2js',
'karma-ng-html2js-preprocessor'],
preprocessors: {
'app/scripts/directives/**/*.html': 'ng-html2js'
},
ngHtml2JsPreprocessor: {
moduleName: 'templates'
}
In my test, I have the following:
'use strict';
describe('Directive: myDirective', function () {
// load the directive's module
beforeEach(module('myApp'));
beforeEach(module('templates'));
var element,
scope;
beforeEach(inject(function ($rootScope) {
scope = $rootScope.$new();
}));
it('should not show search area initially', inject(function ($compile) {
element = angular.element('<navbar></navbar>');
element = $compile(element)(scope);
scope.$digest();
expect(element.find('.myClass').hasClass('myClass')).toBe(true);
}));
});
When I run the test, I get
Error: Unexpected request: GET /scripts/directives/myDirective/myDirective.html
It seems like the preprocessor is not properly injecting the javascript version of the template.
I have also tried using the path of the template in the beforeEach(module('')); but that causes an error that reads:
Error: [$injector:modulerr] Failed to instantiate module...
How can I fix this?
I had kind of the same problem. Be sure you have the exact file match. Open the Google chrome console and check the file path is exactly the same.
In the upper exemple, I had to add a "/" string in ngHtml2JsPreprocessor.stripPrefix and it worked.
So I guess with Yeoman, you should use
ngHtml2JsPreprocessor: {
moduleName: 'templates',
stripPrefix: 'app/' //add a slash
}
Since I was using the Yeoman tool to scaffold my project, I needed to add a stripPrefix to the ngHtml2JsPreprocessor option in my karma.conf.js file:
ngHtml2JsPreprocessor: {
moduleName: 'templates',
stripPrefix: 'app'
}