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:
- Gulp (task runner) on top of Node
- gulp-jasmine (DOM-less simple JavaScript testing framework)
- browserify (require modules in the browser)
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.