KarmaJS, Jasmine, RequireJS, etc: How to Use Require for Testing Modules
jasmine, karma-jasmine, karma-runner, requirejs, unit-testing
Solution
OK, I will try to address each question at a time:
Question 1
- what does { pattern: '...', included: true|false } do?
Karma's default behavior is to:
- find all files matching the `pattern` (mandatory property)
- watch them for changes (`watched` option) in order to restart your unit tests to give you live result when you are editing your code (works if only you leave the default `autoWatch` default value to `true`).
- serve them using its own webserver (`served` option)
- include them in the browser using `<script>` (`included` option)
So, in the `files` array of the karma config you can use default behavior by adding only string patterns:
files: [
// this will match all your JS files
// in the src/ directory and subdirectories
'src/**/*.js'
]
Or use the full object syntax to customize each option:
files: [
{pattern: 'src/**/*.js', watched: true, served: true, included: false}
]
Using requireJS, you DO NOT want them to be included because it will be in conflict with requireJS behavior!
Included. Description: Should the files be included in the browser using `<script>` tag? Use false if you want to load them manually, eg. using Require.js.
from karma/config/files docs
NB: Pay attention to the order in which you add the files/patterns in the array. It matters! For more understanding, set `logLevel: config.LOG_DEBUG` in your karma config.
Question 2
- what files do I need to include in the karma.conf.js file?
At least all required files for the proper functioning of your components for unit testing.
Basically, all files listed in your `define([])` and `require()` blocks.
Question 3
- best way to exclude all the extra stuff inside the bower directories.
What are you trying to do exactly?
Based on what I wrote before, you can see that you can add selectively the files you will need in your tests.
I use to add the pattern `'/bower_components/**/*.js'` and even `'/bower_components/**/*.html'` when my bower packages are using templates. I never noticed any significant performance issue if that's what you are worried about... Up to you to define the file patterns you will need.
Question 4 & 5
what does test-main.js actually do; what's it for?
what files do I need to include in the test-main.js file?
The purpose of the `test-main.js` file is to find and load your test files before starting Karma. It connects the dots between Karma and requireJS
You must choose a convention to name your test files and then define the `TEST_REGEXP` to match all of them.
The "official" angular style guide and best practices for app structure recommends using the suffix `*_test.js`.
EDIT: Your regexp is not working because it is defined to catch `"spec.js" || "test.js"` at the end or your spec file name is ending by `"file.js"` ;) Please see http://regex101.com/r/bE9tV9/1
One more thing
I hope I was clear enough. You can have a look at our starter app structure for our projects using Angular + Require: angular-requirejs-ready. It's already set up and tested with both Karma and Protractor.
Problem
Running Karma + Jasmine Tests with RequireJS -- Getting off the ground Help! . . . _ _ _ . . . SOS! Currently, I have an exercise project up for getting comfortable with KarmaJS -- and Unit Testing, at large. The broad issue is that I really have no transparent view of what Karma is doing behind the scenes, and I can't seem to find adequate documentation in relevant areas. Without further delay... Here is my folder structure: ``` root |-/lib |-/[dependencies] (/angular, /angular-mocks, /bootstrap, /etc) # from bower |-/src |-/[unreferenced directories] (/js, /css, /views) # not referenced anywhere |-app.js # sets up angular.module('app', ...) |-globals.js # may be referenced in RequireJS main file; not used. |-index.html # loads bootstrap.css and RequireJS main file |-main.js # .config + require(['app', 'etc']) |-routeMap.js # sets up a single route |-test-file.js # *** simple define(function(){ return {...}; }) |-/test |-/spec |-test-test-file.js # *** require || define(['test-file']) |-.bowerrc # { "directory": "lib" } |-bower.json # standard format |-karma.conf.js # *** HELP! |-test-main.js # *** Save Our Souls!!! ``` karma.conf.js ``` // Karma configuration // Generated on Wed Nov 19 2014 15:16:56 GMT-0700 (Mountain Standard Time) module.exports = function(config) { config.set({ // base path that will be used to resolve all patterns (eg. files, exclude) basePath: '', // frameworks to use // available frameworks: https://npmjs.org/browse/keyword/karma-adapter frameworks: ['jasmine', 'requirejs'], // list of files / patterns to load in the browser files: [ //'test/spec/test-test-file.js', //'lib/**/*.js', //'src/**/*.js', //'test/spec/**/*.js', 'test-main.js', {pattern: 'lib/**/*.js', included: false}, {pattern: 'src/**/*.js', included: false}, {pattern: 'test/spec/*.js', included: true} ], // list of files to exclude exclude: [ 'lib/**/!(angular|angular-mocks|angular-resource|angular-route|require|text).js', 'lib/**/**/!(jquery|bootstrap).js', 'src/app.js' ], // preprocess matching files before serving them to the browser // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor preprocessors: { }, // test results reporter to use // possible values: 'dots', 'progress' // available reporters: https://npmjs.org/browse/keyword/karma-reporter reporters: ['progress'], // web server port port: 9876, // enable / disable colors in the output (reporters and logs) colors: true, // level of logging // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG logLevel: config.LOG_INFO, // enable / disable watching file and executing tests whenever any file changes autoWatch: true, // start these browsers // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher browsers: ['Chrome'], // Continuous Integration mode // if true, Karma captures browsers, runs the tests and exits singleRun: false }); }; ``` test-main.js ``` var allTestFiles = []; var TEST_REGEXP = /(spec|test)\.js$/i; var pathToModule = function(path) { return path.replace(/^\/base\//, '').replace(/\.js$/, ''); }; Object.keys(window.__karma__.files).forEach(function(file) { if (TEST_REGEXP.test(file)) { // Normalize paths to RequireJS module names. allTestFiles.push(pathToModule(file)); } }); require.config({ // Karma serves files under /base, which is the basePath from your config file baseUrl: '/base/src', paths: { angular: '../lib/angular/angular', ngRoute: '../lib/angular-route/angular-route', jquery: '../lib/jQuery/dist/jquery', bootstrap: '../lib/bootstrap/dist/js/bootstrap', models: 'models', controllers: 'controllers', globals: 'globals', routeMap: 'routeMap' }, shim: { angular: { exports: 'angular' }, ngRoute: { deps: ['angular'] }, jquery: { exports: '$' }, bootstrap: { deps: ['jquery'] } }, // dynamically load all test files deps: allTestFiles, // we have to kickoff jasmine, as it is asynchronous callback: window.__karma__.start }); ``` test-test-file.js ``` console.log('....................'); define(function(){ //console.log('testing test-file', testFile); describe('Testing testing', function(){ it('should work', function(){ expect(true).toEqual(true); }); }); }); ``` test-file.js ``` define('testFile', [], function(){ return function init(sandbox){ var app, application = app = sandbox.app , globals = sandbox.globals; return { some: 'module' }; }; }); ``` Questions & Descriptions Key points I would love to hear answers for are - what does { pattern: '...', include: true|false } do? - best way to exclude all the extra stuff inside the bower directories. - what files do I need to include in the test-main.js file? - what files do I need to include in the karma.conf.js file? - what does test-main.js actually do; what's it for? The times I receive errors & issues is as soon as I wrap my spec in a `define(...)` call -- event when I give the module an ID -- `define('someId', function(){ ... })` -- do I need to return something out of this module, as it is a `define` call? Other times, I receive the 'ol ERROR: 'There is no timestamp for /base/src/app.js!'. "Timestamp, of course! How silly of me..." -- what in the world does this mean?! Sometimes I get the infamous "Executed 0 of 0 ERROR" -- I could also use some clarity here, please. Really, I get plenty of ERROR: '...no timestamp...' errors -- and even `404`s when it seems I should be pulling that library in with the karma.conf.js `files` config...??? It even seems that usually when I explicitly tell karma to exclude`src/app.js` I still get `404`s and errors. tl;dr Obviously, I'm a bit of a confused novice about Karma and *DD at large... I can run `test-test-file.js` fine when my karma.conf.js `files` array looks like `[ 'test-main.js', 'test/spec/test-test-file.js' ]` -- but, still, if I wrap my test in a RequireJS define call I get the "Mismatching anonymous define()" error mentioned above. It seems that when I add { pattern: '...', include: false } then karma just doesn't add any of my files for the given pattern whatsoever (???). If someone can even simply direct me toward how to use RequireJS with Karma -- namely so that I can just wrap my tests in a define/require call and pull in the module I want to test... That would be greatly appreciated. As its somewhat difficult to keep these types of questions short and still provide adequate information, I hope I didn't make it too long. EDIT After reading the answer from glepretre and some fiddling on my own, I reconfigured my project as follows: - Moved `test-main.js` to `test/test-main.js`, - renamed test-test-file.js to testFileSpec.js -- moved it from `test/spec` to `test/`, karma.conf.js: ``` ... // list of files / patterns to load in the browser files: [ {pattern: 'lib/**/*.js', included: false}, {pattern: 'src/**/*.js', included: false}, {pattern: 'test/**/*Spec.js', included: false}, 'test/test-main.js' ], .... ``` test/test-main.js: ``` /* **************** HOW COME THE DEFAULT (Karma-generated) CONFIGURATION DOES ***NOT WORK??? var allTestFiles = []; var TEST_REGEXP = /(spec|test)\.js$/i; var pathToModule = function(path) { return path.replace(/^\/base\//, '').replace(/\.js$/, ''); }; Object.keys(window.__karma__.files).forEach(function(file) { if (TEST_REGEXP.test(file)) { // Normalize paths to RequireJS module names. allTestFiles.push(pathToModule(file)); } }); */ var tests = []; for (var file in window.__karma__.files) { if (/Spec\.js$/.test(file)) { tests.push(file); } } require.config({ // Karma serves files under /base, which is the basePath from your config file baseUrl: '/base/src', paths: {}, shim: {}, // dynamically load all test files //deps: allTestFiles, // deps: tests, // we have to kickoff jasmine, as it is asynchronous callback: window.__karma__.start }); ``` I am now running Unit Tests successfully! Special Thanks to glepretre & all other contributors. Thanks for any insight at all :)