Tag Archives: unit

How to unit test your JS and use it in the browser

Intro

Recently, I wanted to add test coverage to Halloween Bash and keep using it in the browser. This doesn’t seem to be an unreasonable request, but it turns out that it involves many things. You have many choices of test runners & testing frameworks, and I didn’t want to setup a [cci]SpecRunner.html[/cci] to unit test my JS.

The setup that I ended up using is:

What you’ll need:

  • Your HTML/JS project (You can use my demo project)
  • NodeJS (I recommend the Installer from the Node homepage – click “Install”)

Try out the demo app that squares your input.

With my demo, my file structure looks like:

index.html
assets/js
assets/css

There are countless ways to organize your non-html assets, and my demo asset structure is intended to be easy to follow.

My demo contains jquery and two unit testable lib functions (multiply & square):

// define multiply()
window.unitTestJsDemo.multiply = function(x, y) {
  return x*y;
};

// define square()
window.unitTestJsDemo.square = function(x) {
  return unitTestJsDemo.multiply(x, x);
};

Setup NodeJS & GulpJS

Once NodeJS is installed on your machine, setup your node environment:

npm init

The [cci]npm init[/cci] command will walk you through your project, ask you a series of questions, and setup your configuration in [cci]package.json[/cci].

Next, setup Gulp via npm on your command line:

// Install gulp globally
npm install -g gulp

// Install gulp in your project devDependencies
npm install --save-dev gulp

// Create a gulpfile.js at the root of your project
touch gulpfile.js

// gulpfile.js file contents
    var gulp = require('gulp');

    gulp.task('default', function() {
      // place code for your default task here
    }); 

// Run gulp default task
gulp

You now have Node & Gulp setup to run your Gulp tasks. The default Gulp task doesn’t do anything, but you can try it out by running [cci]gulp[/cci].

Setup gulp-jasmine

Save gulp-jasmine into your gulpfile.js:

npm install --save-dev gulp-jasmine

Create your tests:

mkdir -p assets/js/spec/lib
touch assets/js/spec/lib/multiply-spec.js
touch assets/js/spec/lib/square-spec.js

[cci]assets/js/spec/lib/multiply-spec.js[/cci] will contain:

/* jslint node: true */
/* global describe, it, expect */

"use strict";

var multiply_lib = require('../../lib/multiply');

describe("#multiply", function () {
  it("returns the correct multiplied value", function () {
    var product = multiply_lib.multiply(2, 3);
    expect(product).toBe(6);
  });
});

[cci]assets/js/spec/lib/square-spec.js[/cci] will contain:

/* jslint node: true */
/* global describe, it, expect */

"use strict";

var square_lib = require('../../lib/square');

describe("#square", function () {
  it("returns the correct squared value", function () {
    var squared = square_lib.square(3);
    expect(squared).toBe(9);
  });
});

Next, we’re going to move the unit testable functions (multiply & square) into node.js-style modules.

mkdir assets/js/lib
touch assets/js/lib/square.js
touch assets/js/lib/multiply.js

[cci]assets/js/lib/multiply.js[/cci] will contain:

exports.multiply = function(x, y) {

  "use strict";

  return x*y;
};

[cci]assets/js/lib/square.js[/cci] will contain:

var multiply_lib = require('./multiply');

exports.square = function(x) {

  "use strict";

  return multiply_lib.multiply(x, x);
};

Update your [cci]gulpfile.js[/cci] to run the tests:

"use strict";

// Include gulp
var gulp = require('gulp');

// Include plugins
var jasmine = require('gulp-jasmine');

// Test JS
gulp.task('specs', function () {
    return gulp.src('assets/js/spec/lib/*.js')
        .pipe(jasmine());
});

// Default Task
gulp.task('default', function() {
  // place code for your default task here
});

gulp.task('default', ['specs']);

You’ve created your libs (multiply & square), the lib specs (multiply-spec.js & square-spec.js), and setup Gulp to run your tests with Jasmine.

[cci]square.js[/cci] is setup to use the [cci]multiply.js[/cci] lib through the Node require module syntax. Woot!

You can run the default task, which is setup to run your specs task. It should look like:

$ gulp
[16:29:18] Using gulpfile your/path/unit-test-js-demo/gulpfile.js
[16:29:18] Starting 'specs'...
[16:29:18] Finished 'specs' after 44 ms
[16:29:18] Starting 'default'...
[16:29:18] Finished 'default' after 20 μs
..

Finished in 0.008 seconds
2 tests, 2 assertions, 0 failures

Great! Your modules are unit tested (feel free to add more tests), and you want to use them in the browser.

Browserify your JS

Up to this point, we’ve been using jQuery through our local file at [cci]/assets/js/jquery-1.10.2.min.js[/cci]. We’ll want to get rid of managing jQuery ourselves and let Node manage our jQuery dependency.

Let’s create a new file for our page’s JS to organize itself around:

touch assets/js/app.js

Add jQuery to your dependencies and remove your local copy of jQuery:

npm install --save-dev jquery
rm assets/js/jquery-1.10.2.min.js

[cci]assets/js/app.js[/cci] will contain:

var $           = require('jquery');
var square_lib  = require('./lib/square');

$(function() {

  "use strict";

  $("#squareValue").change(function() {
    var $this       = $(this),
        squareValue = $this.val(),
        squareResult;

    // if squareValue is not numeric
    if (isNaN(squareValue)) {

      $("#squareResult").html('N/A');
      return false;

    // else squareValue is numeric
    } else {

      squareResult = square_lib.square(squareValue);
      $("#squareResult").html(squareResult);
      return true;
    }
  });

});

Now we need to use Browserify to build our JS file with gulp.

Add Browserify related dependencies into your gulpfile and setup your new task:

npm install --save-dev gulp-uglify
npm install --save-dev vinyl-source-stream
npm install --save-dev gulp-streamify
npm install --save-dev browserify

Update your [cci]gulpfile.js[/cci] to include the new browserify task:

"use strict";

// Include gulp
var gulp = require('gulp');

// Include plugins
var jasmine     = require('gulp-jasmine');
var uglify      = require('gulp-uglify');
var source      = require('vinyl-source-stream'); // makes browserify bundle compatible with gulp
var streamify   = require('gulp-streamify');
var browserify  = require('browserify');

// Test JS
gulp.task('specs', function () {
    return gulp.src('assets/js/spec/lib/*.js')
        .pipe(jasmine());
});

// Concatenate, Browserify & Minify JS
gulp.task('scripts', function() {
    return browserify('./assets/js/app.js').bundle()
        .pipe(source('all.min.js'))
        .pipe(streamify(uglify()))
        .pipe(gulp.dest('./public/'));
});

// Default Task
gulp.task('default', function() {
  // place code for your default task here
});

gulp.task('default', ['specs', 'scripts']);

You’ll notice that we did a few things: declare new modules at the top through [cci]require[/cci], add a new gulp task called [cci]scripts[/cci], and update the default task to run our JS specs & scripts tasks.

At [cci]public/all.min.js[/cci], your new JS is ready to use in your browser.

Let’s remove the old file and update our [cci]index.html[/cci] to use our new minified JS:

rm assets/js/main.js 

Remove the following lines from [cci]index.html[/cci]:

< script src="assets/js/jquery-1.10.2.min.js">
< script src="assets/js/main.js">

Add the following line into [cci]index.html[/cci]:

< script src="public/all.min.js">

Voila! Open up [cci]index.html[/cci] in your browser and your [cci]square()[/cci] function is working again.

Conclusion

GulpJS is an amazing tool to run tasks, and the Gulp streaming build system is very easy to read and understand.

There are countless tasks that you can setup on Gulp to lint your JS, compile your sass, etc. Livereload is useful for front end development.

Hopefully, the Unit Test JS demo helped you understand a simple example of using Gulp to run Jasmine unit tests and use the tested JS in your website.