{"id":1858,"date":"2014-07-19T17:54:29","date_gmt":"2014-07-19T21:54:29","guid":{"rendered":"http:\/\/www.rexfeng.com\/blog\/?p=1858"},"modified":"2014-07-19T18:20:12","modified_gmt":"2014-07-19T22:20:12","slug":"how-to-unit-test-your-js-and-use-it-in-the-browser","status":"publish","type":"post","link":"https:\/\/www.rexfeng.com\/blog\/2014\/07\/how-to-unit-test-your-js-and-use-it-in-the-browser\/","title":{"rendered":"How to unit test your JS and use it in the browser"},"content":{"rendered":"<h1>Intro<\/h1>\n<p>Recently, I wanted to add test coverage to <a href=\"https:\/\/github.com\/xta\/HalloweenBash\">Halloween Bash<\/a> and keep using it in the browser. This doesn&#8217;t seem to be an\u00a0unreasonable request, but it turns out that it involves many things. You have many choices of test runners &amp; testing frameworks, and I didn&#8217;t want to setup a\u00a0[cci]SpecRunner.html[\/cci] to unit test my JS.<\/p>\n<p>The setup that I ended up using is:<\/p>\n<ul>\n<li><a href=\"http:\/\/gulpjs.com\/\">Gulp<\/a> (task runner) on top of <a href=\"http:\/\/nodejs.org\/\">Node<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/sindresorhus\/gulp-jasmine\">gulp-jasmine<\/a> (DOM-less simple JavaScript testing framework)<\/li>\n<li><a href=\"http:\/\/browserify.org\/\">browserify<\/a> (require modules in the browser)<\/li>\n<\/ul>\n<p>What you&#8217;ll need:<\/p>\n<ul>\n<li>Your HTML\/JS project (You can use my <a href=\"https:\/\/github.com\/xta\/unit-test-js-demo\/tree\/initial\">demo project<\/a>)<\/li>\n<li>NodeJS (I recommend the Installer from\u00a0the <a href=\"http:\/\/nodejs.org\/\">Node homepage<\/a>\u00a0&#8211; click &#8220;Install&#8221;)<\/li>\n<\/ul>\n<p><a href=\"http:\/\/xta.github.io\/unit-test-js-demo\/\">Try out the demo app that squares your input<\/a>.<\/p>\n<p>With my demo, my file structure looks like:<\/p>\n<pre>index.html\r\nassets\/js\r\nassets\/css\r\n<\/pre>\n<p>There are countless ways to organize your non-html assets, and my demo asset structure is intended to be\u00a0easy to follow.<\/p>\n<p>My demo contains jquery and two unit testable lib functions (multiply &amp; square):<\/p>\n<pre>\/\/ define multiply()\r\nwindow.unitTestJsDemo.multiply = function(x, y) {\r\n  return x*y;\r\n};\r\n\r\n\/\/ define square()\r\nwindow.unitTestJsDemo.square = function(x) {\r\n  return unitTestJsDemo.multiply(x, x);\r\n};\r\n<\/pre>\n<h1>Setup NodeJS &amp; GulpJS<\/h1>\n<p>Once NodeJS is installed on your machine, setup your node environment:<\/p>\n<pre>npm init<\/pre>\n<p>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].<\/p>\n<p>Next, <a href=\"https:\/\/github.com\/gulpjs\/gulp\/blob\/master\/docs\/getting-started.md#getting-started\">setup\u00a0Gulp<\/a>\u00a0via npm on your command line:<\/p>\n<pre>\/\/ Install gulp globally\r\nnpm install -g gulp\r\n\r\n\/\/ Install gulp in your project devDependencies\r\nnpm install --save-dev gulp\r\n\r\n\/\/ Create a gulpfile.js at the root of your project\r\ntouch gulpfile.js\r\n\r\n\/\/ gulpfile.js file contents\r\n    var gulp = require('gulp');\r\n\r\n    gulp.task('default', function() {\r\n      \/\/ place code for your default task here\r\n    }); \r\n\r\n\/\/ Run gulp default task\r\ngulp\r\n<\/pre>\n<p>You now have Node &amp; Gulp setup to run your Gulp tasks. The default Gulp task doesn&#8217;t do anything, but you can try it out by running [cci]gulp[\/cci].<\/p>\n<h1>Setup gulp-jasmine<\/h1>\n<p>Save gulp-jasmine into your gulpfile.js:<\/p>\n<pre>npm install --save-dev gulp-jasmine\r\n<\/pre>\n<p>Create your tests:<\/p>\n<pre>mkdir -p assets\/js\/spec\/lib\r\ntouch assets\/js\/spec\/lib\/multiply-spec.js\r\ntouch assets\/js\/spec\/lib\/square-spec.js\r\n<\/pre>\n<p>[cci]assets\/js\/spec\/lib\/multiply-spec.js[\/cci] will contain:<\/p>\n<pre>\/* jslint node: true *\/\r\n\/* global describe, it, expect *\/\r\n\r\n\"use strict\";\r\n\r\nvar multiply_lib = require('..\/..\/lib\/multiply');\r\n\r\ndescribe(\"#multiply\", function () {\r\n  it(\"returns the correct multiplied value\", function () {\r\n    var product = multiply_lib.multiply(2, 3);\r\n    expect(product).toBe(6);\r\n  });\r\n});\r\n<\/pre>\n<p>[cci]assets\/js\/spec\/lib\/square-spec.js[\/cci] will contain:<\/p>\n<pre>\/* jslint node: true *\/\r\n\/* global describe, it, expect *\/\r\n\r\n\"use strict\";\r\n\r\nvar square_lib = require('..\/..\/lib\/square');\r\n\r\ndescribe(\"#square\", function () {\r\n  it(\"returns the correct squared value\", function () {\r\n    var squared = square_lib.square(3);\r\n    expect(squared).toBe(9);\r\n  });\r\n});<\/pre>\n<p>Next, we&#8217;re going to move the unit testable functions (multiply &amp; square) into\u00a0node.js-style modules.<\/p>\n<pre>mkdir assets\/js\/lib\r\ntouch assets\/js\/lib\/square.js\r\ntouch assets\/js\/lib\/multiply.js\r\n<\/pre>\n<p>[cci]assets\/js\/lib\/multiply.js[\/cci] will contain:<\/p>\n<pre>exports.multiply = function(x, y) {\r\n\r\n  \"use strict\";\r\n\r\n  return x*y;\r\n};<\/pre>\n<p>[cci]assets\/js\/lib\/square.js[\/cci] will contain:<\/p>\n<pre>var multiply_lib = require('.\/multiply');\r\n\r\nexports.square = function(x) {\r\n\r\n  \"use strict\";\r\n\r\n  return multiply_lib.multiply(x, x);\r\n};<\/pre>\n<p>Update\u00a0your\u00a0[cci]gulpfile.js[\/cci]\u00a0to run the tests:<\/p>\n<pre>\"use strict\";\r\n\r\n\/\/ Include gulp\r\nvar gulp = require('gulp');\r\n\r\n\/\/ Include plugins\r\nvar jasmine = require('gulp-jasmine');\r\n\r\n\/\/ Test JS\r\ngulp.task('specs', function () {\r\n    return gulp.src('assets\/js\/spec\/lib\/*.js')\r\n        .pipe(jasmine());\r\n});\r\n\r\n\/\/ Default Task\r\ngulp.task('default', function() {\r\n  \/\/ place code for your default task here\r\n});\r\n\r\ngulp.task('default', ['specs']);\r\n\r\n<\/pre>\n<p>You&#8217;ve created your libs (multiply &amp; square), the lib specs (multiply-spec.js &amp; square-spec.js), and setup Gulp to run your tests with Jasmine.<\/p>\n<p>[cci]square.js[\/cci] is setup to use the\u00a0[cci]multiply.js[\/cci] lib through the Node require module syntax. Woot!<\/p>\n<p>You can run the default task, which is setup to run your specs task. It should look like:<\/p>\n<pre>$ gulp\r\n[16:29:18] Using gulpfile your\/path\/unit-test-js-demo\/gulpfile.js\r\n[16:29:18] Starting 'specs'...\r\n[16:29:18] Finished 'specs' after 44 ms\r\n[16:29:18] Starting 'default'...\r\n[16:29:18] Finished 'default' after 20 \u03bcs\r\n..\r\n\r\nFinished in 0.008 seconds\r\n2 tests, 2 assertions, 0 failures<\/pre>\n<p>Great! Your modules are unit tested (feel free to add more tests), and you want to use them in the browser.<\/p>\n<h1>Browserify your JS<\/h1>\n<p>Up to this point, we&#8217;ve been using\u00a0jQuery through our local file at [cci]\/assets\/js\/jquery-1.10.2.min.js[\/cci]. We&#8217;ll want to get rid of managing\u00a0jQuery ourselves\u00a0and let Node manage our jQuery\u00a0dependency.<\/p>\n<p>Let&#8217;s create a new file for our page&#8217;s JS to organize itself around:<\/p>\n<pre>touch assets\/js\/app.js<\/pre>\n<p>Add jQuery to your dependencies and remove your local copy of jQuery:<\/p>\n<pre>npm install --save-dev jquery\r\nrm assets\/js\/jquery-1.10.2.min.js<\/pre>\n<p>[cci]assets\/js\/app.js[\/cci] will contain:<\/p>\n<pre>var $           = require('jquery');\r\nvar square_lib  = require('.\/lib\/square');\r\n\r\n$(function() {\r\n\r\n  \"use strict\";\r\n\r\n  $(\"#squareValue\").change(function() {\r\n    var $this       = $(this),\r\n        squareValue = $this.val(),\r\n        squareResult;\r\n\r\n    \/\/ if squareValue is not numeric\r\n    if (isNaN(squareValue)) {\r\n\r\n      $(\"#squareResult\").html('N\/A');\r\n      return false;\r\n\r\n    \/\/ else squareValue is numeric\r\n    } else {\r\n\r\n      squareResult = square_lib.square(squareValue);\r\n      $(\"#squareResult\").html(squareResult);\r\n      return true;\r\n    }\r\n  });\r\n\r\n});<\/pre>\n<p>Now we need to use Browserify to build our JS file with gulp.<\/p>\n<p>Add Browserify related dependencies into your gulpfile and setup your new task:<\/p>\n<pre>npm install --save-dev gulp-uglify\r\nnpm install --save-dev vinyl-source-stream\r\nnpm install --save-dev gulp-streamify\r\nnpm install --save-dev browserify<\/pre>\n<p>Update your [cci]gulpfile.js[\/cci] to include the new browserify task:<\/p>\n<pre>\"use strict\";\r\n\r\n\/\/ Include gulp\r\nvar gulp = require('gulp');\r\n\r\n\/\/ Include plugins\r\nvar jasmine     = require('gulp-jasmine');\r\nvar uglify      = require('gulp-uglify');\r\nvar source      = require('vinyl-source-stream'); \/\/ makes browserify bundle compatible with gulp\r\nvar streamify   = require('gulp-streamify');\r\nvar browserify  = require('browserify');\r\n\r\n\/\/ Test JS\r\ngulp.task('specs', function () {\r\n    return gulp.src('assets\/js\/spec\/lib\/*.js')\r\n        .pipe(jasmine());\r\n});\r\n\r\n\/\/ Concatenate, Browserify &amp; Minify JS\r\ngulp.task('scripts', function() {\r\n    return browserify('.\/assets\/js\/app.js').bundle()\r\n        .pipe(source('all.min.js'))\r\n        .pipe(streamify(uglify()))\r\n        .pipe(gulp.dest('.\/public\/'));\r\n});\r\n\r\n\/\/ Default Task\r\ngulp.task('default', function() {\r\n  \/\/ place code for your default task here\r\n});\r\n\r\ngulp.task('default', ['specs', 'scripts']);<\/pre>\n<p>You&#8217;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 &amp; scripts tasks.<\/p>\n<p>At [cci]public\/all.min.js[\/cci], your new JS is ready to use in your browser.<\/p>\n<p>Let&#8217;s remove the old file and update our [cci]index.html[\/cci] to use our new minified JS:<\/p>\n<pre>rm assets\/js\/main.js \r\n<\/pre>\n<p>Remove the following lines from [cci]index.html[\/cci]:<\/p>\n<pre>&lt; script src=\"assets\/js\/jquery-1.10.2.min.js\"&gt;\r\n&lt; script src=\"assets\/js\/main.js\"&gt;<\/pre>\n<p>Add the following line into [cci]index.html[\/cci]:<\/p>\n<pre>&lt; script src=\"public\/all.min.js\"&gt;<\/pre>\n<p>Voila! Open up [cci]index.html[\/cci] in your browser and your [cci]square()[\/cci] function is working again.<\/p>\n<h1>Conclusion<\/h1>\n<p>GulpJS is an amazing tool to run tasks, and the Gulp streaming build system\u00a0is very easy to read and understand.<\/p>\n<p>There are countless tasks that you can setup on Gulp to lint your JS,\u00a0compile your sass, etc. <a href=\"https:\/\/github.com\/vohof\/gulp-livereload\">Livereload<\/a> is useful for front end development.<\/p>\n<p>Hopefully, the\u00a0<a href=\"https:\/\/github.com\/xta\/unit-test-js-demo\">Unit Test JS demo<\/a> helped you understand a simple example of using Gulp to run Jasmine unit tests and use\u00a0the tested JS in your website.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Intro Recently, I wanted to add test coverage to Halloween Bash and keep using it in the browser. This doesn&#8217;t seem to be an\u00a0unreasonable request, but it turns out that it involves many things. You have many choices of test runners &amp; testing frameworks, and I didn&#8217;t want to setup a\u00a0[cci]SpecRunner.html[\/cci] to unit test my [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1029],"tags":[1142,1153,1058,1145,1146,1054,1151,1148,1147,1152,1150,1096,1144,1143],"class_list":["post-1858","post","type-post","status-publish","format-standard","hentry","category-programming","tag-bdd","tag-browserify","tag-dom","tag-gulp","tag-gulpjs","tag-js","tag-modules","tag-node","tag-nodejs","tag-require","tag-tasks","tag-tdd","tag-test-2","tag-unit"],"_links":{"self":[{"href":"https:\/\/www.rexfeng.com\/blog\/wp-json\/wp\/v2\/posts\/1858","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.rexfeng.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.rexfeng.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.rexfeng.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.rexfeng.com\/blog\/wp-json\/wp\/v2\/comments?post=1858"}],"version-history":[{"count":8,"href":"https:\/\/www.rexfeng.com\/blog\/wp-json\/wp\/v2\/posts\/1858\/revisions"}],"predecessor-version":[{"id":1866,"href":"https:\/\/www.rexfeng.com\/blog\/wp-json\/wp\/v2\/posts\/1858\/revisions\/1866"}],"wp:attachment":[{"href":"https:\/\/www.rexfeng.com\/blog\/wp-json\/wp\/v2\/media?parent=1858"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.rexfeng.com\/blog\/wp-json\/wp\/v2\/categories?post=1858"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.rexfeng.com\/blog\/wp-json\/wp\/v2\/tags?post=1858"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}