How to test Durandal.js viewmodel containing i18next implementation?

1.4k views Asked by At

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 },
1

There are 1 answers

1
Hallvar Helleseth On

Hallais Remi! You can initialize i18next in beforeEach like this:

define(['viewModels/ViewModel', 'i18next'], function (ViewModel, i18n) {
    describe("Viewmodel test", function () {
        beforeEach(function() {
            var initialized = false;

            runs(function() {
                i18n.init({
                    detectFromHeaders: false,
                    fallbackLng: 'en',
                    preload: ['nb', 'en'],
                    supportedLngs: ['nb', 'en'],
                    resGetPath: 'locales/__lng__.json',
                    useCookie: true
                }, function () { initialized = true; });
            });

            waitsFor(function() {
                return initialized;
            }, 'i18n to initialize', 100);
        });

        it("should be testable", function () {
            expect(true).toEqual(true);
        });
        it("should have title", function () {
            expect(ViewModel.pageTitle).not.toBeEmpty();
        });
    });
});

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