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.

SCARF: a brain-based collaboration model

At YC’s Startup School NYC, Shana Fisher brought up an amazing resource called SCARF.

SCARF stands for Status, Certainty, Autonomy, Relatedness and Fairness.

With these five areas, stands positive (approach) and negative (avoid) modes of interacting with people. As the title says, SCARF is based on neuroscience research to correlate human interaction & collaboration with physical brain activity.

When you are being threatened with a SCARF metric, you will perform worse (due to “less oxygen and glucose available for the brain functions involved in working memory“). On the contrary, when you have an opportunity to increase a SCARF metric, you perform better since you don’t have decreased cognitive resources.

There are lots of findings and useful applications, so I encourage you to read about SCARF.

Profit Center Based Work

When it comes to work that will build a great career, Patrick McKenzie has great advice:

Profit Centers are the part of an organization that bring in the bacon: partners at law firms, sales at enterprise software companies, “masters of the universe” on Wall Street, etc etc. Cost Centers are, well, everybody else. You really want to be attached to Profit Centers because it will bring you higher wages, more respect, and greater opportunities for everything of value to you.

The gist is that you want your role at the company to correlate with revenue generation.

If your role leads to more revenue, your role will always be in demand.  Sales functions have so much money allocated into them with the expectation that it is an investment. Back office tasks (HR, accounting, etc.) are essential, but they do not generate gross revenue and are always on the chopping block.

Maintainable Client JavaScript

I’ve been reading Maintainable JavaScript by Nicholas C. Zakas lately. It has been very insightful into best practices across larger teams. When you have a large team and adopt a consistent coding style, it makes working across your codebase easier.

In Chapter 5 of the book, Zakas covers the UI as a mix of HTML, CSS, and Javascript. He mentions that tightly coupling these three layers makes it “impossible to make small changes without changing one or two other layers.

Also, I’ve been working with AngularJS recently. I understand the benefits of a front end framework to keep data in sync throughout your client. Angular fans tout the benefits of a SPA (single page application) framework.

As someone who strives to separate the structure (HTML) from the scripting (JS), Angular feels too tightly coupled to me. Angular works by tagging everything with [cci]ng-[/cci] and letting the magic work behind the scenes. The application dependencies are hardcoded everywhere in your HTML, and there is no way to swap your framework without changing your HTML drastically.

I’ve worked with Backbone in the past, and now I’m trying out Angular. At some point, I’ll probably try out Ember. I’d like a front end framework that plays well with Rails, so perhaps Ember will be fun.

Leaflet JS with Open MapQuest Tiles

With CloudMade changing their API to focus on enterprise customers, I had to find an alternative for hosted map tiles.

There’s a good gist from mourner that shows Leaflet.js TileLayer shortcuts at https://gist.github.com/mourner/1804938

Instead of the old CloudMade tile tutorial used on Leaflet:

[cc]

L.tileLayer(‘http://{s}.tile.cloudmade.com/API-key/997/256/{z}/{x}/{y}.png’, {
attribution: ‘proper attribution goes here’
}).addTo(map);

[/cc]

You can use Open MapQuest:

[cc]

L.tileLayer(‘http://{s}.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.png’, {
attribution: ‘proper attribution goes here’,
subdomains: [‘otile1’, ‘otile2’, ‘otile3’, ‘otile4’]
}).addTo(map);

[/cc]

Just make sure your attribution properly reflects all your sources.

Developer Education

As an avid tech news reader, I’m able to follow industry discussions about hot new start ups.

For example, startups such as Snapchat, Pinterest, etc. have garnered a lot of hype. As a technology enthusiast, what is my duty to try out new apps / sites?

Is it enough to know about them? Is it enough to have tried them? Is it enough to know how to recreate them?

Misc Rails Integration Test Tips

I attended an Automated Testing meetup yesterday. In my experience, integration tests turn your speedy test suite into a slowpoke. Here are some different tips and gems to consider using in your tests:

  • rack_session_access Gem
    Instead of making a user log in with capybara, why not set the user_id in the session. This gem sounds amazing.
  • fuubar Gem
    Another way to format your test results. Perhaps you’d like to use a progress slider.
  • rspec-retry Gem
    If a test does not pass on the first try, you can run it again until it passes. This feels like a code smell. In the meetup, the speaker mentioned that they were running their tests against live web requests (instead of pre-recorded requests).
  • Page Objects
    Capybara syntax seems procedural since you are telling it to visit a path and click on things. With page objects, you can have DRY code that abstracts away all the step by step page interactions with something like [cci]ProjectPageObject.create[/cci]
  • View Tests
    Instead of doing full blown integration (feature) testing that uses a browser, you can try view tests that assert things are found on your page.

Hopefully you can use some of these tips to improve your test suite.

Ruby Gems Tip: Check Your Version

Quick ruby gems tip:

If you are having issues with a gem (and it seems like the issue has been resolved in the github repo), double check your gem version.

Even though I had the latest version of a gem by having [cci]gem ‘leaflet-rails'[/cci] in my Gemfile, the latest version wasn’t bumped up at http://rubygems.org/ (Note: Nothing against leaflet-rails, which is awesome. They’re simply used as a recent, real-life example. )

You can make sure you’re using the latest version of a gem (as it appears on github) by specifying the repo & commit hash:

[cc]gem ‘leaflet-rails’, git: ‘git@github.com:axyjo/leaflet-rails.git’, ref: ‘0f50faaa35d41e8ba24c73c97d265e061b159d81′[/cc]

This will lock the specific commit in place in your Gemfile.lock