{"id":3743,"date":"2014-03-19T14:18:09","date_gmt":"2014-03-19T22:18:09","guid":{"rendered":"http:\/\/blog.mozilla.org\/webdev\/?p=3743"},"modified":"2014-03-19T14:18:09","modified_gmt":"2014-03-19T22:18:09","slug":"building-a-javascript-library-with-tests-mocks-and-ci","status":"publish","type":"post","link":"https:\/\/blog.mozilla.org\/webdev\/2014\/03\/19\/building-a-javascript-library-with-tests-mocks-and-ci\/","title":{"rendered":"Building A JavaScript Library With Tests, Mocks, and CI"},"content":{"rendered":"<p>I talked recently at DjangoCon about <a href=\"https:\/\/github.com\/kumar303\/build-open-source\">how to build better Python packages<\/a>. As engineers we all use each other\u2019s open source libraries everyday so it\u2019s nice when 1) they work, 2) they\u2019re well documented, and 3) they\u2019re well tested.<\/p>\n<p>For a recent project I set out to build a <a href=\"https:\/\/github.com\/mozilla\/fxpay\">JavaScript library<\/a> for other developers to use in their web applications. It had been a while since I made a browser library in JavaScript so I took it as an opportunity to see if I could apply some Python packaging concepts to JavaScript.<\/p>\n<p>This is a brief summary of some tools and patterns I settled on. There are plenty of alternatives so if you have suggestions for improvement, please comment!<\/p>\n<h2>Project Tasks<\/h2>\n<p>With any project it\u2019s inevitable that you\u2019ll begin writing scripts for maintenance. I decided to go with <a href=\"http:\/\/gruntjs.com\/\">Grunt<\/a> because it keeps me in the realm of JavaScript (even though the developer needs NodeJS installed). Plus there are lots of contributed Grunt tasks to choose from. For example, to minify my library at <code>lib\/myscript.js<\/code> all I had to do was install <a href=\"https:\/\/www.npmjs.org\/package\/uglify-js\">uglify-js<\/a>, <a href=\"https:\/\/github.com\/gruntjs\/grunt-contrib-uglify\">grunt-contrib-uglify<\/a>, and add a Gruntfile.js with this:<br \/>\n<script src=\"https:\/\/gist.github.com\/kumar303\/9652343.js\"><\/script><br \/>\nNow I can type this to minify the code:<\/p>\n<pre>grunt uglify<\/pre>\n<h2>Running Unit Tests<\/h2>\n<p>All libraries (even JavaScript ones!) need automated tests otherwise you and future collaborators will find the library really hard to maintain. If you try to make a change to code without tests you have to <em>think<\/em> about every possible side effect or you have to manually test all side effects. Both of these things seem easy when you only have one or two functions but they become exponentially harder as your library grows.<\/p>\n<p>I decided to try <a href=\"http:\/\/karma-runner.github.io\/\">Karma<\/a> to run tests in a real web browser, <a href=\"http:\/\/visionmedia.github.io\/mocha\/\">mocha<\/a> for my test cases, and <a href=\"http:\/\/chaijs.com\/\">chai<\/a> for assertions. There are a few alternatives but Karma seemed like the best choice for a runner. (I found out about <a href=\"http:\/\/yeti.cx\/\">Yeti<\/a> after I settled on Karma; I haven&#8217;t tried it.)<\/p>\n<p>Karma lets you run tests from your console against one or more web browsers in parallel. It\u2019s exactly what I wish existed back when I wrote <a href=\"https:\/\/github.com\/kumar303\/jstestnet\">jstestnet<\/a>! Karma is really fast and seems to work pretty well. Speed is essential in testing.<\/p>\n<p>To get started, you can type <a href=\"http:\/\/karma-runner.github.io\/0.10\/intro\/configuration.html\">karma init karma.conf.js<\/a> to create a config file and you can hook it up to Grunt with <a href=\"https:\/\/github.com\/karma-runner\/grunt-karma\">grunt-karma<\/a> in a Gruntfile like this:<br \/>\n<script src=\"https:\/\/gist.github.com\/kumar303\/9652380.js\"><\/script><br \/>\nThis fit well with my project since I was already using Grunt. I can now type:<\/p>\n<pre>grunt test<\/pre>\n<p>Karma will open all target web browsers (per config file), run my test suite, report results, and shut down the web browsers.<\/p>\n<p>The Karma output is pretty minimal. It looks something like this in a shell:<\/p>\n<p><a href=\"https:\/\/blog.mozilla.org\/webdev\/files\/2014\/03\/Screen-Shot-2014-03-19-at-2.56.49-PM.png\"><img decoding=\"async\" loading=\"lazy\" class=\"alignnone size-full wp-image-3748\" alt=\"Screen Shot 2014-03-19 at 2.56.49 PM\" src=\"https:\/\/blog.mozilla.org\/webdev\/files\/2014\/03\/Screen-Shot-2014-03-19-at-2.56.49-PM.png\" width=\"599\" height=\"217\" srcset=\"https:\/\/blog.mozilla.org\/webdev\/files\/2014\/03\/Screen-Shot-2014-03-19-at-2.56.49-PM.png 599w, https:\/\/blog.mozilla.org\/webdev\/files\/2014\/03\/Screen-Shot-2014-03-19-at-2.56.49-PM-252x91.png 252w\" sizes=\"(max-width: 599px) 100vw, 599px\" \/><\/a><\/p>\n<p>For development I also added this command:<br \/>\n<script src=\"https:\/\/gist.github.com\/kumar303\/9652404.js\"><\/script><br \/>\nWith that I can type:<\/p>\n<pre>grunt karma:dev<\/pre>\n<p>This opens all target web browsers, runs the test suite, and keeps the web browsers running. As I edit files, it re-runs the tests. This seems to work okay but occasionally I\u2019ve seen some timeout errors that go away if I restart the browsers. I\u2019m still looking into that.<\/p>\n<h2>Writing Tests<\/h2>\n<p><a href=\"http:\/\/visionmedia.github.io\/mocha\/\">Mocha<\/a> is a testing library that works in NodeJS and also in web browsers. It uses the <a href=\"http:\/\/en.wikipedia.org\/wiki\/Behavior-driven_development\">BDD<\/a> (behavioral driven development) style of specifying an object or function and declaring how it should behave. Here is an example from my library that covers some error handling:<br \/>\n<script src=\"https:\/\/gist.github.com\/kumar303\/9652415.js\"><\/script><br \/>\nAs you can see it&#8217;s pretty easy to read the code and see what is being tested. Both <a href=\"http:\/\/visionmedia.github.io\/mocha\/\">Mocha<\/a> and <a href=\"http:\/\/jasmine.github.io\/\">Jasmine<\/a> have Karma adapters and there are probably other adapters too if you want to use another test library.<\/p>\n<h2>Testing With Mock Objects<\/h2>\n<p>A common pattern in testing is to mock out objects that are used by your system but that you don&#8217;t need to test. <a href=\"http:\/\/sinonjs.org\/\">Sinon<\/a> is designed exactly for this and especially for testing in the web browser. My library has a thin API layer around <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/XMLHttpRequest\">XMLHttpRequest<\/a> but I wanted to mock that out while testing the API layer. Here is an example of making sure it gets an error callback for 500 responses:<br \/>\n<script src=\"https:\/\/gist.github.com\/kumar303\/9652425.js\"><\/script><br \/>\nThis is nice because I can run my tests without the code touching a real API server. I&#8217;m using Sinon&#8217;s <a href=\"http:\/\/sinonjs.org\/docs\/#fakeServer\">Fake Server<\/a> here.<\/p>\n<p>It&#8217;s easy to go overboard with mocks once you discover their power. My words of caution is that anytime you mock out an object you are deciding <em>not<\/em> to test something. Make sure that&#8217;s the right decision. Make sure it gives you real benefits like a speedup or something. It&#8217;s usually a good idea to mock out HTTP connections since otherwise your tests depend on the Internet.<\/p>\n<h2>Continuous Integration<\/h2>\n<p>Testing is most effective when you run your tests after every code commit. There is a free service for open source projects called <a href=\"https:\/\/travis-ci.org\/\">TravisCI<\/a> which supports running browser tests with Karma just fine. You can run all your tests in a headless browser like <a href=\"http:\/\/phantomjs.org\/\">PhantomJS<\/a> with a task like this:<br \/>\n<script src=\"https:\/\/gist.github.com\/kumar303\/9652443.js\"><\/script><br \/>\nAdd the grunt command to a <code>.travis.yml<\/code> file to hook it up:<br \/>\n<script src=\"https:\/\/gist.github.com\/kumar303\/9652453.js\"><\/script><br \/>\nHowever, my specific library will benefit most from running its tests in a real Firefox and TravisCI <a href=\"http:\/\/docs.travis-ci.com\/user\/gui-and-headless-browsers\/\">supports web browsers<\/a> just fine so, hey, why not. All I had to do was add this to my yaml file:<br \/>\n<script src=\"https:\/\/gist.github.com\/kumar303\/9652462.js\"><\/script><br \/>\nFirefox is pre-installed on TravisCI so I didn\u2019t even need to declare it.<\/p>\n<h2>Checking For Syntax Errors<\/h2>\n<p>JavaScript is a quirky language (to put it nicely) so I always make sure to run the code through something like <a href=\"http:\/\/www.jshint.com\/\">JS Hint<\/a> to catch undeclared variables and other problems. You can add syntax checking easily to grunt like this using <a href=\"https:\/\/github.com\/gruntjs\/grunt-contrib-jshint\">grunt-contrib-jshint<\/a>:<br \/>\n<script src=\"https:\/\/gist.github.com\/kumar303\/9652470.js\"><\/script><br \/>\nThis lets me type <code>grunt jshint<\/code> to check for syntax errors in all lib and test files as well as in my Gruntfile.js. I use a <a href=\"http:\/\/www.jshint.com\/docs\/options\/\">.jshintrc<\/a> file to set common options that I like.<\/p>\n<p>My continuous integration script actually runs JS Hint before running unit tests. I chain all these together like this at the bottom of my Gruntfile:<\/p>\n<pre>grunt.registerTask('test', ['jshint', 'karma:run']);<\/pre>\n<p>Now when I run <code>grunt test<\/code> the jshint and karma:run tasks are executed.<\/p>\n<h2>Documentation<\/h2>\n<p>All packages need documentation! I like to start with realistic scenarios to illustrate usage of the library. After that I make sure to fully document all public APIs. I tend to just put docs in a Github README and then switch to <a href=\"http:\/\/sphinx-doc.org\/\">Sphinx<\/a> and <a href=\"https:\/\/readthedocs.org\/\">Read The Docs<\/a> later when the docs are too big for a single README. Sphinx doesn\u2019t support JavaScript as well as Python but it\u2019s still probably the best tool out there for managing interlinked docs.<\/p>\n<h2>Packaging<\/h2>\n<p>To make a library useful to others you may wish to package it up. To be honest I haven\u2019t done this to my library yet but I was considering <a href=\"http:\/\/bower.io\/\">bower<\/a> and\/or <a href=\"https:\/\/www.npmjs.org\/doc\/json.html\">NodeJS packaging<\/a>.<\/p>\n<h2>That\u2019s It<\/h2>\n<p>Since I left out a lot of details, you may want to check the <a href=\"https:\/\/github.com\/mozilla\/fxpay\">actual code<\/a> for working examples.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I talked recently at DjangoCon about how to build better Python packages. As engineers we all use each other\u2019s open source libraries everyday so it\u2019s nice when 1) they work, 2) they\u2019re well documented, and 3) they\u2019re well tested. For &hellip; <a class=\"go\" href=\"https:\/\/blog.mozilla.org\/webdev\/2014\/03\/19\/building-a-javascript-library-with-tests-mocks-and-ci\/\">Continue reading<\/a><\/p>\n","protected":false},"author":293,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4713,4730,28306,288],"tags":[],"coauthors":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v22.5 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Building A JavaScript Library With Tests, Mocks, and CI  - Mozilla Web Development<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/blog.mozilla.org\/webdev\/2014\/03\/19\/building-a-javascript-library-with-tests-mocks-and-ci\/\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"kumar303\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"6 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/blog.mozilla.org\/webdev\/2014\/03\/19\/building-a-javascript-library-with-tests-mocks-and-ci\/\",\"url\":\"https:\/\/blog.mozilla.org\/webdev\/2014\/03\/19\/building-a-javascript-library-with-tests-mocks-and-ci\/\",\"name\":\"Building A JavaScript Library With Tests, Mocks, and CI - Mozilla Web Development\",\"isPartOf\":{\"@id\":\"https:\/\/blog.mozilla.org\/webdev\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/blog.mozilla.org\/webdev\/2014\/03\/19\/building-a-javascript-library-with-tests-mocks-and-ci\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/blog.mozilla.org\/webdev\/2014\/03\/19\/building-a-javascript-library-with-tests-mocks-and-ci\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/blog.mozilla.org\/webdev\/files\/2014\/03\/Screen-Shot-2014-03-19-at-2.56.49-PM.png\",\"datePublished\":\"2014-03-19T22:18:09+00:00\",\"dateModified\":\"2014-03-19T22:18:09+00:00\",\"author\":{\"@id\":\"https:\/\/blog.mozilla.org\/webdev\/#\/schema\/person\/aa2b14e555e8a5fec38a51659b15e3c4\"},\"breadcrumb\":{\"@id\":\"https:\/\/blog.mozilla.org\/webdev\/2014\/03\/19\/building-a-javascript-library-with-tests-mocks-and-ci\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/blog.mozilla.org\/webdev\/2014\/03\/19\/building-a-javascript-library-with-tests-mocks-and-ci\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/blog.mozilla.org\/webdev\/2014\/03\/19\/building-a-javascript-library-with-tests-mocks-and-ci\/#primaryimage\",\"url\":\"https:\/\/blog.mozilla.org\/webdev\/files\/2014\/03\/Screen-Shot-2014-03-19-at-2.56.49-PM.png\",\"contentUrl\":\"https:\/\/blog.mozilla.org\/webdev\/files\/2014\/03\/Screen-Shot-2014-03-19-at-2.56.49-PM.png\",\"width\":599,\"height\":217},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/blog.mozilla.org\/webdev\/2014\/03\/19\/building-a-javascript-library-with-tests-mocks-and-ci\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/blog.mozilla.org\/webdev\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Building A JavaScript Library With Tests, Mocks, and CI\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/blog.mozilla.org\/webdev\/#website\",\"url\":\"https:\/\/blog.mozilla.org\/webdev\/\",\"name\":\"Mozilla Web Development\",\"description\":\"For make benefit of glorious tubes\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/blog.mozilla.org\/webdev\/?s={search_term_string}\"},\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/blog.mozilla.org\/webdev\/#\/schema\/person\/aa2b14e555e8a5fec38a51659b15e3c4\",\"name\":\"kumar303\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/blog.mozilla.org\/webdev\/#\/schema\/person\/image\/a707ad1bf04083698e2b4a775a7492b7\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/059920b9daee1ece045f4031037ffb79?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/059920b9daee1ece045f4031037ffb79?s=96&d=mm&r=g\",\"caption\":\"kumar303\"},\"description\":\"Kumar hacks on Mozilla web services and tools for various projects, such as those supporting Firefox Add-ons. He hacks on lots of random open source projects too.\",\"sameAs\":[\"http:\/\/farmdev.com\/\",\"https:\/\/www.facebook.com\/kumar303\",\"https:\/\/x.com\/kumar303\"],\"url\":\"https:\/\/blog.mozilla.org\/webdev\/author\/kmcmillanmozilla-com\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Building A JavaScript Library With Tests, Mocks, and CI  - Mozilla Web Development","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/blog.mozilla.org\/webdev\/2014\/03\/19\/building-a-javascript-library-with-tests-mocks-and-ci\/","twitter_misc":{"Written by":"kumar303","Est. reading time":"6 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/blog.mozilla.org\/webdev\/2014\/03\/19\/building-a-javascript-library-with-tests-mocks-and-ci\/","url":"https:\/\/blog.mozilla.org\/webdev\/2014\/03\/19\/building-a-javascript-library-with-tests-mocks-and-ci\/","name":"Building A JavaScript Library With Tests, Mocks, and CI - Mozilla Web Development","isPartOf":{"@id":"https:\/\/blog.mozilla.org\/webdev\/#website"},"primaryImageOfPage":{"@id":"https:\/\/blog.mozilla.org\/webdev\/2014\/03\/19\/building-a-javascript-library-with-tests-mocks-and-ci\/#primaryimage"},"image":{"@id":"https:\/\/blog.mozilla.org\/webdev\/2014\/03\/19\/building-a-javascript-library-with-tests-mocks-and-ci\/#primaryimage"},"thumbnailUrl":"https:\/\/blog.mozilla.org\/webdev\/files\/2014\/03\/Screen-Shot-2014-03-19-at-2.56.49-PM.png","datePublished":"2014-03-19T22:18:09+00:00","dateModified":"2014-03-19T22:18:09+00:00","author":{"@id":"https:\/\/blog.mozilla.org\/webdev\/#\/schema\/person\/aa2b14e555e8a5fec38a51659b15e3c4"},"breadcrumb":{"@id":"https:\/\/blog.mozilla.org\/webdev\/2014\/03\/19\/building-a-javascript-library-with-tests-mocks-and-ci\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/blog.mozilla.org\/webdev\/2014\/03\/19\/building-a-javascript-library-with-tests-mocks-and-ci\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/blog.mozilla.org\/webdev\/2014\/03\/19\/building-a-javascript-library-with-tests-mocks-and-ci\/#primaryimage","url":"https:\/\/blog.mozilla.org\/webdev\/files\/2014\/03\/Screen-Shot-2014-03-19-at-2.56.49-PM.png","contentUrl":"https:\/\/blog.mozilla.org\/webdev\/files\/2014\/03\/Screen-Shot-2014-03-19-at-2.56.49-PM.png","width":599,"height":217},{"@type":"BreadcrumbList","@id":"https:\/\/blog.mozilla.org\/webdev\/2014\/03\/19\/building-a-javascript-library-with-tests-mocks-and-ci\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/blog.mozilla.org\/webdev\/"},{"@type":"ListItem","position":2,"name":"Building A JavaScript Library With Tests, Mocks, and CI"}]},{"@type":"WebSite","@id":"https:\/\/blog.mozilla.org\/webdev\/#website","url":"https:\/\/blog.mozilla.org\/webdev\/","name":"Mozilla Web Development","description":"For make benefit of glorious tubes","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/blog.mozilla.org\/webdev\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/blog.mozilla.org\/webdev\/#\/schema\/person\/aa2b14e555e8a5fec38a51659b15e3c4","name":"kumar303","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/blog.mozilla.org\/webdev\/#\/schema\/person\/image\/a707ad1bf04083698e2b4a775a7492b7","url":"https:\/\/secure.gravatar.com\/avatar\/059920b9daee1ece045f4031037ffb79?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/059920b9daee1ece045f4031037ffb79?s=96&d=mm&r=g","caption":"kumar303"},"description":"Kumar hacks on Mozilla web services and tools for various projects, such as those supporting Firefox Add-ons. He hacks on lots of random open source projects too.","sameAs":["http:\/\/farmdev.com\/","https:\/\/www.facebook.com\/kumar303","https:\/\/x.com\/kumar303"],"url":"https:\/\/blog.mozilla.org\/webdev\/author\/kmcmillanmozilla-com\/"}]}},"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/blog.mozilla.org\/webdev\/wp-json\/wp\/v2\/posts\/3743"}],"collection":[{"href":"https:\/\/blog.mozilla.org\/webdev\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.mozilla.org\/webdev\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.mozilla.org\/webdev\/wp-json\/wp\/v2\/users\/293"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.mozilla.org\/webdev\/wp-json\/wp\/v2\/comments?post=3743"}],"version-history":[{"count":0,"href":"https:\/\/blog.mozilla.org\/webdev\/wp-json\/wp\/v2\/posts\/3743\/revisions"}],"wp:attachment":[{"href":"https:\/\/blog.mozilla.org\/webdev\/wp-json\/wp\/v2\/media?parent=3743"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.mozilla.org\/webdev\/wp-json\/wp\/v2\/categories?post=3743"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.mozilla.org\/webdev\/wp-json\/wp\/v2\/tags?post=3743"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/blog.mozilla.org\/webdev\/wp-json\/wp\/v2\/coauthors?post=3743"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}