DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
The Latest "Software Integration: The Intersection of APIs, Microservices, and Cloud-Based Systems" Trend Report
Get the report
  1. DZone
  2. Coding
  3. JavaScript
  4. How to write and unit-test universal JavaScript modules (browser, Node.js)

How to write and unit-test universal JavaScript modules (browser, Node.js)

Axel Rauschmayer user avatar by
Axel Rauschmayer
·
Aug. 29, 11 · Interview
Like (0)
Save
Tweet
Share
8.35K Views

Join the DZone community and get the full member experience.

Join For Free

Node.js has a very nice module system that is easy to understand, yet distinguishes between the exports of a module and things that should be private to it. This post explains how the code of a Node.js module can be modified so that it works on both Node.js and web browsers. It also explains how to unit-test such code.

1. Writing a universal module

1.1. Node.js modules

A Node.js module looks as follows:
    var privateVariable = 123;
    function privateFunction() {
    }
    
    exports.publicVariable = 345;
    exports.publicFunction = function () {
        return privateVariable * exports.publicVariable;
    }
You add your public data to the object exports which Node.js manages for you. Additionally, Node.js ensures that all other data stays private to your module. Inside the module, you refer to private data directly, to public data via exports. See below for how such a module is used.

1.2. Making Node.js code universal

You can use Node.js module code in browsers, if you wrap it as follows:
    "use strict";

    (function(exports) {
        
        // Your Node.js code goes here

    }(typeof exports === "undefined" ? (this.moduleName = {}) : exports));
Let’s examine the wrapper code:
  • Enabling strict mode: The first line enables strict mode [1] under ECMAScript 5 and does nothing under older versions of JavaScript.
  • An immediately invoked function definition (IIFE): We define a function and immediately invoke it [2]. This serves two purposes:
    • It keeps non-exported data private in browsers, where Node.js does not do this for us.
    • It conditionally creates the variable exports which exists on Node.js, but must be created in a browser. Creating a new scope with a new variable exports in it is the only way of doing this, you cannot add a variable to an existing scope if it isn’t there, yet. That is, you cannot do the following:
          if (typeof exports !== "undefined") {
              var exports = {}; // (*)
          }
      
      Reason: The declaration (but not the assignment!) at (*) is hoisted to the beginning of the current function, which means that exports will always be undefined when the check is performed. Note that this would work without hoisting, but not with block-scoping, because then the exports declared at (*) would only exist inside the then-block.
  • typeof exports === "undefined": Does the variable exports exist?
  • this.moduleName = {}: At the global level, this refers to the global object. Therefore, this assignment creates the global variable moduleName (the name of the module in a browser).
Running example:
    "use strict";

    (function(exports) {

        exports.StringSet = function () {
            this.data = {};
        }

        exports.StringSet.prototype.add = function(elem) {
            if (typeof elem !== "string") {
                throw new TypeError("Argument is not a string: "+elem);
            }
            this.data[elem] = true;
        }

        exports.StringSet.prototype.contains = function(elem) {
            // Comparison ensures boolean result
            return this.data[elem] === true;
        }
        
        exports.StringSet.prototype.copy = function() {
            var result = new exports.StringSet();
            Object.keys(this.data).forEach(function(elem) {
                result.add(elem);
            });
            return result;
        }

    }(typeof exports === "undefined" ? (this.strset = {}) : exports));

2. Using a universal module

Node:
    var strset = require("./strset");
    var s = new strset.StringSet();
Browser:
    <script src="strset.js"></script>
    <script>
        var s = new strset.StringSet();
    </script>

3. Unit-testing a universal module

3.1. Node.js: unit-testing via the module assert

We use Node.js’ built-in means for unit-testing – the module assert.
    var assert = require('assert');
    var strset = require('./strset');

    // contains
    (function() {
        var sset = new strset.StringSet().add("a");
        assert.ok(sset.contains("a"));
        assert.ok(!sset.contains("b"));
    }());

    // copy
    (function() {
        var copy = new strset.StringSet().add("a").copy(); 
        assert.ok(copy.contains("a"));
        assert.ok(!copy.contains("b"));
    }());

    // add - illegal arguments
    (function() {
        assert.throws(function() {
            new strset.StringSet().add(3);
        });
    }());
These tests are run by storing them in a file strset-node-test.js and executing it via the command line:
    > node strset-node-test.js
If one of the tests fails, there will be an exception. If all tests succeed, nothing happens.

3.2. Browser: unit-testing via jQuery’s QUnit

In a browser, we have (too) many options: Most frameworks come with their own unit-testing support. Let us use jQuery’s QUnit.
<!doctype html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        <title>strset-test</title>
        <script src="http://code.jquery.com/jquery-latest.js"></script>
        <link rel="stylesheet" href="http://code.jquery.com/qunit/git/qunit.css" type="text/css" media="screen">
        <script type="text/javascript" src="http://code.jquery.com/qunit/git/qunit.js"></script>
        <script type="text/javascript" src="strset.js"></script>
        <script>
            $(document).ready(function(){
                test("contains", function() {
                    var sset = new strset.StringSet().add("a");
                    ok(sset.contains("a"));
                    ok(!sset.contains("b"));
                });
                test("copy", function() {
                    var copy = new strset.StringSet().add("a").copy(); 
                    ok(copy.contains("a"));
                    ok(!copy.contains("b"));
                });
                test("add - illegal arguments", function() {
                    raises(function() {
                        new strset.StringSet().add(3);
                    });
                });
            });
        </script>
    </head>
    <body>
        <h1 id="qunit-header">strset-test</h1>
        <h2 id="qunit-banner"></h2>
        <div id="qunit-testrunner-toolbar"></div>
        <h2 id="qunit-userAgent"></h2>
        <ol id="qunit-tests"></ol>
        <div id="qunit-fixture">test markup, will be hidden</div>
    </body>
</html>
You execute these tests by saving them in an .html file and opening it in a browser.

4. Related reading

Related reading
  1. JavaScript’s strict mode: a summary
  2. JavaScript variable scoping and its pitfalls
  3. Modules and namespaces in JavaScript
  4. pattern for module targeting browser and nodejs. Inspiration for the above pattern. Does the same thing, but in reverse – one uses the same variable to export public data on both Node.js and browsers. On Node.js that variable points to the exports object, on browsers, it points to an object in a global variable.

 

From http://www.2ality.com/2011/08/universal-modules.html

unit test Node.js JavaScript

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • What Is Advertised Kafka Address?
  • Testing Level Dynamics: Achieving Confidence From Testing
  • What Is JavaScript Slice? Practical Examples and Guide
  • Distributed Tracing: A Full Guide

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: