I'm new to both Durandal.js and Javascript testing with Jasmine, and need some help. I have managed to get a testing environment up and running with Karma and Jasmine, but I struggle to figure out how to test my viewmodels that contains i18next dependencies.
Here's a stripped down example of my setup:
main.js
requirejs.config({
paths: {
'models': 'models',
'text': '../lib/require/text',
'durandal': '../lib/durandal/js',
'plugins': '../lib/durandal/js/plugins',
'transitions': '../lib/durandal/js/transitions',
'knockout': '../lib/knockout/knockout-2.3.0',
'bootstrap': '../lib/bootstrap/js/bootstrap.min',
'jquery': '../lib/jquery/jquery-1.9.1',
'i18next': '../lib/i18next/i18next.amd.withJQuery-1.7.1.min'
}
});
define(['plugins/router', 'durandal/system', 'durandal/app', 'durandal/binder', 'durandal/viewLocator', 'knockout', 'jquery', 'i18next'], function (router, system, app, binder, viewLocator, ko, $, i18n) {
app.configurePlugins({
router: true
});
app.start().then(function () {
// Setup of i18n
i18n.init({
detectFromHeaders: false,
fallbackLng: 'en',
preload: ['nb', 'en'],
supportedLngs: ['nb', 'en'],
resGetPath: 'locales/__lng__.json',
useCookie: true,
}, function () {
binder.binding = function (obj, view) {
$(view).i18n();
};
viewLocator.useConvention();
app.setRoot('viewmodels/shell');
});
});
});
shell.js
define(['plugins/router', 'durandal/app', 'i18next'], function (router, app, i18n) {
return {
router: router,
activate: function () {
return router.map([
{ route: '', title: i18n.t('pageTitle'), moduleId: 'viewmodels/ViewModel', nav: true }
]).buildNavigationModel().mapUnknownRoutes('viewmodels/ViewModel', 'unknown').activate({
pushState: false,
hashChange: true
});
}
};
});
test.js (own require.js config)
var tests = [];
for (var file in window.__karma__.files) {
if (window.__karma__.files.hasOwnProperty(file)) {
if (/.*\.spec\.js$/.test(file)) {
tests.push(file);
}
}
}
//Workaround for the timestamp issue
for (var file in window.__karma__.files) {
window.__karma__.files[file.replace(/^\//, '')] = window.__karma__.files[file];
}
require.config({
baseUrl: 'base',
paths: {
'models': 'app/models',
'viewModels': 'app/viewmodels',
'specs': 'tests/specs/',
'text': 'lib/require/text',
'durandal': 'lib/durandal/js',
'plugins': 'lib/durandal/js/plugins',
'transitions': 'lib/durandal/js/transitions',
'knockout': 'lib/knockout/knockout-2.3.0',
'jquery': 'lib/jquery/jquery-1.9.1',
'i18next': 'lib/i18next/i18next.amd.withJQuery-1.7.1.min',
},
// ask Require.js to load these files (all our tests)
deps: tests,
// start test run, once Require.js is done
callback: window.__karma__.start
});
karma.conf.js
// Karma configuration
module.exports = function (config) {
config.set({
// base path, that will be used to resolve files and exclude
basePath: '',
// frameworks to use
frameworks: ['jasmine', 'requirejs'],
// list of files / patterns to load in the browser
files: [
{ pattern: 'lib/jquery/jquery-1.9.1.js', watched: false, included: true, served: true },
{ pattern: 'tests/jasmine/jasmine-jquery.js', watched: false, served: true, included: true },
{ pattern: 'tests/specs/**/*.js', included: false },
{ pattern: 'tests/sinon.js', included: false },
{ pattern: 'lib/**/*.js', included: false, served: true },
{ pattern: 'app/**/*.js', included: false, served: true },
'tests/test.js',
//Serve the fixtures
{ pattern: 'app/**/*.html', watched: true, served: true, included: false },
{ pattern: 'app/**/*.json', watched: true, served: true, included: false },
{ pattern: 'tests/**/*.json', watched: true, served: true, included: false }
],
preprocessors: [
{ '**/*.html': '' }
],
// list of files to exclude
exclude: [
'foundation/*.js',
'require.js',
'init.js',
'jasmine/jasmine.js',
'jasmine/jasmine-html.js',
'node_modules/**/*.html'
],
// test results reporter to use
// possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'
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, currently available:
// - Chrome
// - ChromeCanary
// - Firefox
// - Opera
// - Safari (only Mac)
// - PhantomJS
// - IE (only Windows)
browsers: ['PhantomJS'],
//browsers: ['Chrome', 'Firefox', 'IE'],
// If browser does not capture in given timeout [ms], kill it
captureTimeout: 60000,
// Continuous Integration mode
// if true, it capture browsers, run tests and exit
singleRun: false
});
};
ViewModel (i18next):
define(["i18next"], function (i18n) {
var pageTitle = i18n.t("pageTitle");
return {
pageTitle: pageTitle
};
});
Spec (i18next)
define(['viewModels/ViewModel'], function (ViewModel) {
describe("Viewmodel test", function () {
it("should be testable", function () {
expect(true).toEqual(true);
});
it("should have title", function () {
expect(ViewModel.pageTitle).not.toBeEmpty();
});
});
});
Test runner output
PhantomJS 1.9.2 (Mac OS X) ERROR ReferenceError: Can't find variable: initialized at /Users/xxx/dev/projectXYZ/lib/i18next/i18next.amd.withJQuery-1.7.1.min.js:5 PhantomJS 1.9.2 (Mac OS X): Executed 0 of 0 ERROR (0.312 secs / 0 secs)
ViewModel (wo/i18next):
define([], function () {
var pageTitle = "This is the page title";
return {
pageTitle: pageTitle
};
});
Spec (wo/i18next)
define(['viewModels/ViewModel'], function (ViewModel) {
describe("Viewmodel test", function () {
it("should be testable", function () {
expect(true).toEqual(true);
});
it("should have title", function () {
expect(ViewModel.pageTitle).toEqual("This is the page title");
});
});
});
Test runner output (wo/i18next)
PhantomJS 1.9.2 (Mac OS X): Executed 2 of 2 SUCCESS (0.312 secs / 0.164 secs)
I guess I have to somehow initialize i18next, but I dont know how.
Any help would be much appreciated.
Regards,
Remi
UPDATE
This is what I ended up doing:
test.js:
'i18next-original': 'lib/i18next/i18next.amd.withJQuery-1.7.1.min',
'i18next': 'tests/i18n-test'
i18n-test.js:
define(['i18next-original'], function (i18noriginal) {
'use strict';
i18noriginal.init({
lng: 'nb',
detectFromHeaders: false,
fallbackLng: 'en',
preload: ['nb', 'en'],
supportedLngs: ['nb', 'en'],
resGetPath: 'base/locales/__lng__.json',
useCookie: false,
debug: false,
getAsync: false
});
return i18noriginal;
});
karma.conf.js:
{ pattern: 'tests/i18n-test.js', included: false, served: true },
Hallais Remi! You can initialize i18next in beforeEach like this:
For focusing the test on code inside the viewModel itself you can consider instead to mock the i18n object used in your viewModel.
testr.js might help you mock require.js dependencies: https://github.com/medallia/testr.js