Developing on Staxmanade

Running in-app mocha tests within WinJS

(Comments)

I described a while back a scenario about running in-app unit tests. So if you'd like some background on the subject have a look there before reading here.

This post is going to give some specifics about how to run MochaJS tests within a Microsoft Windows JavaScript application (WinJS).

Prerequisites

These steps assume you already have a WinJS application, possibly using the universal template or other. In the end it doesn't matter. As long as you have a project that probably has a default.html file and the ability to add js & css files to.

Get Mocha

You can acquire the mocha library however you want, Bower, Npm, or download it manually from the site (mochajs.org).

Reference Mocha Within Project & App

However you get the mocha source, you need to both add references to the mocha js and css files into your project file either in a *.jsproj file or if using a universal shared app, in the shared project.

Then you need to include a reference to the code in your default.html file.

In my example below you can see I used bower to download the MochaJS library.

The mocha.setup('bdd') tells mocha to use the BDD style of tests which defines the describe(...), it(...), etc functions.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>MyApp.Windows</title>

    <!-- WinJS references -->
    <link href="/js/bower_components/winjs/css/ui-light.css" rel="stylesheet" />
    <script src="/js/bower_components/winjs/js/base.js"></script>
    <script src="/js/bower_components/winjs/js/ui.js"></script>

    <!-- TESTS-->
    <link href="/js/bower_components/mocha/mocha.css" rel="stylesheet" />
    <script src="/js/bower_components/mocha/mocha.js"></script>
    <script>
        mocha.setup('bdd');
    </script>

    <!-- My Project js/css files below... -->

...Rest of default.html file excluded

Create a WinJS Control for hosting Mocha reporting.

Below is a sample WinJS control that can be used to host the mocha html report.

<!DOCTYPE html>
<html>
<head>
    <style>
        #mocha {
            height: 800px;
            width: 600px;
            overflow: scroll;
        }
    </style>
</head>
<body>
    <div class="fragment section1page">
        <section aria-label="Main content" role="main">

            <!-- define a button that we can use to manually run or re-run tests -->
            <button id="mochaTestsRun">Run Tests</button>

            <!-- define a checkbox that allow us to toggle auto-run
                 of the tests when we start up the app -->
            Auto start <input type="checkbox" id="mochaTestsRunOnStart" />


            <!-- this is a blank div we use to inject U.I. related tests -->
            <div id="testElementContainer"></div>

            <!-- mocha throws the html output in the below div -->
            <div id="mocha"></div>
        </section>
    </div>
</body>
</html>
(function () {
    "use strict";

    var runMochaTests = function() {
        // clear any current test results since mocha just appends.
        element.querySelector('#mocha').innerHTML = "";

        // If you want your tests to verify nothing is leaked globally...
        mocha.checkLeaks();

        // specify any globals that you are OK with your tests leaking
        mocha.globals([
            'someGlobalIAmExpecting'
        ]);

        // start the test run
        mocha.run();
    }

    var ControlConstructor = WinJS.UI.Pages.define("/js/tests/tests.html", {
        // This function is called after the page control contents
        // have been loaded, controls have been activated, and
        // the resulting elements have been parented to the DOM.
        ready: function (element, options) {
            options = options || {};

            // get our possibly already cached value of whether to run the tests on startup.
            var runOnStart = (JSON.parse(localStorage.getItem("mochaTestsRunOnStart") || "true"));

            // checkbox to manage auto-run state
            var runOnStartCheckbox = element.querySelector('#mochaTestsRunOnStart');
            runOnStartCheckbox.addEventListener('change', function () {
                localStorage.setItem("mochaTestsRunOnStart", runOnStartCheckbox.checked);
            });

            // button to manually trigger a test run
            var mochaTestsRunButton = element.querySelector('#mochaTestsRun');
            mochaTestsRunButton.addEventListener('click', function(){
                runMochaTests();
            })
            runOnStartCheckbox.checked = runOnStart;

            if (runOnStart && !window.hasRunMochaTestsAtLeastOnce) {
                // this value is used to avoid extra test runs as we navigation around the app.
                // EX: if the test control is on a home pivot - we nav away and come back home
                // we probably don't want to auto-run the tests again. (use the menu button
                // instead if you want another test run)
                window.hasRunMochaTestsAtLeastOnce = true;
                runMochaTests();
            }

        },
    });

    // The following lines expose this control constructor as a global.
    // This lets you use the control as a declarative control inside the
    // data-win-control attribute.

    WinJS.Namespace.define("MyApp.Testing.Controls", {
        TestsControl: ControlConstructor
    });
})();

Handle global exceptions

If you write any async code (difficult not to these days) an exception or assertion failure will not be trapped by the internal try/catch mechanism's of Mocha in a Windows WinJS environment.

We have to give Mocha a hint on how to hook into global exceptions.

Mocha tries to attach to the browser's global window.onerror method, and since a WinJS app doesn't use this same handler, we have to forward the exceptions and to mocha's attached window.onerror handler.

In your default.js or wherever you configure the app you can attach to the WinJS.Application.onerror and after some exception massaging we can hand the exceptions to mocha so when a test fails it can be reported correctly.

    WinJS.Application.onerror = function (ex) {

        var errorMessage, errorLine, errorUrl;
        if (ex.detail.errorMessage) {
            errorMessage = ex.detail.errorMessage;
            errorLine = ex.detail.errorLine;
            errorUrl = ex.detail.errorUrl;
        } else if (ex &&
            ex.detail &&
            ex.detail.exception &&
            ex.detail.exception.stack) {
            errorMessage = ex.detail.exception.stack;
            errorLine = null;
            errorUrl = null;
        }

        // if window.onerror exists, assume mochajs is here and call it's error handler
        // This may be a poor assumption because 3rd party libraries could also attach
        // their handlers, but it's working for me so far...
        if (window.onerror && errorMessage) {
            window.onerror(errorMessage, errorLine, errorUrl);

            // return true signalling that the error's been
            // handled (keeping the whole app from crashing)
            return true;
        }

        // if we get here, assuming mochajs isn't running
        // let's log out the errors...
        try {
            var exMessage = JSON.stringify(ex, null, '  ');
            console.error(exMessage);
        } catch (e) {
            // can't JSON serialize exception object here (probably circular reference)
            // log what we can...
            console.error(ex);
            console.error(e);
        }

        // I like to be stopped while debugging to possibly
        // poke around and do further inspection
        debugger;
    }

Reference the test control.

While developing out the application, I like to throw it front and center in the first hub of my apps home page hub control.

Here's an example of how to reference the above control:

<head>
    <meta charset="utf-8" />
    <title>hubPage</title>

    <link href="/pages/hub/hub.css" rel="stylesheet" />
    <script src="/js/tests/tests.js"></script>
</head>

...page details trimmed for brevity...

            <div class="hub" data-win-control="WinJS.UI.Hub">

                <div class="sectionTests"
                     data-win-control="WinJS.UI.HubSection"
                     data-win-options="{ isHeaderStatic: true }"
                     data-win-res="{ winControl: {'header': 'TestSection'} }">
                    <div id="sectionTestscontenthost" data-win-control="MyApp.Testing.TestsControl"></div>
                </div>
            </div>

...rest of page details trimmed for brevity...

If you manage to get everything wired up correctly above, when you run the app (F5) your mocha tests should be all set and run automatically. Oh wait, let's not forget to add a mocha test :)...

describe('Mocha test spike', function(){

    it("should report a passing test", function(){
      // doing nothing should be a passing test
    });

    it("should fail on a synchronous exception", function(){
        throw new Error("Some test error to make sure mocha reports a test failure")
    });

    it("should fail on an asynchronous exception", function(done){
        setTimeout(function(){
            throw new Error("Some test error to make sure mocha reports an async test failure")
            done();
        }, 100);
        throw new Error("Some test error to make sure mocha reports a test failure")
    });

});

Save the above as your first test file, include it so it runs on startup and verify your tests run and report correctly within the test control.

Happy testing!

Comments