Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

Test Drive Node.js

Jay Harris
August 29, 2012

Test Drive Node.js

If you don’t test it, how do you know it works? Over the past few years, we have been compelled to write unit and integration tests for our applications--code that validates code--and it is these tests that change a one-off tool into a well-architected, robust, business-ready application. Yet, every new framework requires a new testing framework, so in this session, we will discuss testing frameworks for node.js. You will walk away with a solid understanding of how to write tests against your node.js applications and modules, leading to confidence that your work is business-ready.

Author:
Jay Harris
Problem Solver | Arana Software
[email protected] | www.aranasoft.com
www.twitter.com/jayharris

Jay Harris

August 29, 2012
Tweet

More Decks by Jay Harris

Other Decks in Technology

Transcript

  1. Node.js is a platform built on Chrome's JavaScript runtime for

    easily building fast, scalable network applications. “ ” What is node.js?
  2. TDD is to coding style as yoga is to posture.

    Even when you're not actively practicing, having done so colors your whole life healthier.” j. kerr “
  3. assert(value)            .ok(value)      

         .equal(actual,  expected)            .notEqual(actual,  expected)            .deepEqual(actual,  expected)            .notDeepEqual(actual,  expected)            .strictEqual(actual,  expected)            .notStrictEqual(actual,  expected)            .throws(block,  [error])            .doesNotThrow(block,  [error])            .ifError(value)
  4. var assert = require('assert'); // Will pass assert.ok(true); // Will

    throw an exception assert.ok(false); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  5. var assert = require('assert'); // Will throw 'false == true'

    error assert.ok(typeof 'hello' === 'number'); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  6. $  node  test.js assert.js:104    throw  new  assert.AssertionError({    

               ^ AssertionError:  false  ==  true        at  Object.<anonymous>  (my-­‐test.js:7:8)        at  Module._compile  (module.js:449:26)        at  Object.Module._extensions..js  (module.js:467:10)        at  Module.load  (module.js:356:32)        at  Function.Module._load  (module.js:312:12)        at  Module.runMain  (module.js:487:10)        at  process.startup.processNextTick.process._tick... $  _
  7. isTrue,                  

       isFalse, isNull,                      isNotNull, isUndefined,            isDefined, isFunction,              isNotFunction, isArray,                    isNotArray, isBoolean,                isNotBoolean, isNumber,                  isNotNumber, isString,                  isNotString,                                    include,                                    lengthOf,                                    operator,                                    closeTo   isObject,                  isNotObject, typeOf,                      notTypeOf, instanceOf,              notInstanceOf, match,                        notMatch, property,                  notProperty, deepProperty,          notDeepProperty, propertyVal,            propertyNotVal, deepPropertyVal,    deepPropertyNotVal, additional assertions
  8. var assert = require('chai').assert; // Will throw 'expected 'hello' to

    be a number' assert.isNumber('hello'); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  9. var chai = require('chai') , assert = chai.assert; chai.Assertion.includeStack =

    true; // Will throw and display stack assert.isNumber('hello'); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  10. $  node  my-­‐test.js assert.js:104    throw  new  assert.AssertionError({    

               ^ AssertionError:  false  ==  true        at  Object.<anonymous>  (my-­‐test.js:7:8)        at  Module._compile  (module.js:449:26)        at  Object.Module._extensions..js  (module.js:467:10)        at  Module.load  (module.js:356:32)        at  Function.Module._load  (module.js:312:12)        at  Module.runMain  (module.js:487:10)        at  process.startup.processNextTick.process._tick... $  _
  11. $  npm  install  -­‐g  mocha $  mkdir  test $  mocha

           ✔  0  tests  complete  (1ms) $  
  12. suite('Testing  out  this  thing',  function()  {        test('should

     do  stuff',  function()  {                //  Assertion  tests        }); }); tddSyntax
  13. ./test/my-test.js var assert = require('chai').assert; suite('Assertions', function() { test('should pass

    on truthiness', function() { assert.ok(true); }); test('should fail on falsiness', function() { assert.ok(false); }); }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
  14. $  mocha  -­‐-­‐ui  tdd  -­‐-­‐reporter  spec    Assertions    

       ✓  should  pass  on  truthiness          1)  should  fail  on  falsiness    ✖  1  of  2  tests  failed:    1)  Assertions  should  fail  on  falsiness:              AssertionError:  false  ==  true            at  (stack  trace  omitted  for  brevity) $  _
  15. suite('Testing  out  this  thing',  function()  {        suite('with

     a  subset  of  this  other  thing',  function()  {                test('should  do  stuff',  function()  {                        //  Assertion  tests                });        }); }); groupSyntax
  16. var assert = require('chai').assert; suite('Assertions', function() { suite('of truthiness', function()

    { test('should pass on true', function() { assert.isTrue(true); }); test('should pass on false', function() { assert.isFalse(false); }); }); suite('of type', function() { test('should pass on number', function() { assert.isNumber(5); }); }); }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  17. $  mocha  -­‐-­‐ui  tdd  -­‐-­‐reporter  spec    Assertions    

       of  truthiness            ✓  should  pass  on  true              ✓  should  pass  on  false          of  type            ✓  should  pass  on  number      ✔  3  tests  complete  (6ms) $  _
  18. suite('Testing  out  this  thing',  function()  {        suite('with

     a  subset  of  this  other  thing',  function()  {                test('should  do  stuff  someday');        }); }); pendingSyntax
  19. var assert = require('chai').assert; suite('Assertions', function() { suite('of truthiness', function()

    { test('should pass on true', function() { assert.isTrue(true); }); test('should pass on false', function() { assert.isFalse(false); }); }); suite('of type', function() { test('should pass on number', function() { assert.isNumber(5); }); test('should pass on object'); }); }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  20. $  mocha  -­‐-­‐ui  tdd  -­‐-­‐reporter  spec    Assertions    

       of  truthiness            ✓  should  pass  on  true              ✓  should  pass  on  false          of  type            ✓  should  pass  on  number              -­‐  should  pass  on  object    ✔  4  tests  complete  (6ms)    •  1  test  pending $  _
  21. suite('Testing  out  this  thing',  function()  {        setup(function(){

                   //  ...        };        suite('with  a  subset  of  this  other  thing',  function()  {                test('should  do  stuff',  function()  {                        //  Assertion  tests                });                teardown(function(){                        //  ...                };        }); }); setupTeardown
  22. test('it  should  not  error',  function(done)  {        search.find("Apples",

     done); }); test('it  should  return  2  items',  function(done)  {        search.find("Apples",  function(err,  res)  {                if  (err)  return  done(err);                res.should.have.length(2);                done();        }); }); asynchronousSyntax
  23. suite('When searching for Apples', function(done) { setup(function(done){ items.save(['fiji apples', 'empire

    apples'], done); }); test('it should not error', function(done) { search.find("Apples", done); }); test('it should return 2 items', function(done) { search.find("Apples", function(err, res) { if (err) return done(err); res.should.have.length(2); done(); }); }); }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  24. be nice to yourself: $  mocha  -­‐-­‐ui  tdd  -­‐-­‐reporter  spec

    should be simplified to $  make  test and $  npm  test
  25. ./makefile # Makefile for sample module test: mocha --reporter spec

    --ui tdd .PHONY: test 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
  26. ./makefile # Makefile for sample module test: mocha \ --reporter

    spec \ --ui tdd .PHONY: test 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
  27. ./package.json { "name": "sample", "version": "0.1.0", "devDependencies": { "chai": "~1.2.0"

    }, "scripts": { "test": "make test" } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
  28. $  make  test    Assertions        ✓  should

     pass  on  truthiness          1)  should  fail  on  falsiness    ✖  1  of  2  tests  failed:    1)  Assertions  should  fail  on  falsiness:              AssertionError:  false  ==  true            at  (stack  trace  omitted  for  brevity) $  _
  29. $  npm  test    Assertions        ✓  should

     pass  on  truthiness          1)  should  fail  on  falsiness    ✖  1  of  2  tests  failed:    1)  Assertions  should  fail  on  falsiness:              AssertionError:  false  ==  true            at  (stack  trace  omitted  for  brevity) $  _
  30. $  npm  install  mocha  -­‐-­‐save-­‐dev $  npm  install  chai  

     -­‐-­‐save-­‐dev $  git  diff  package.json   diff  -­‐-­‐git  a/package.json  b/package.json index  439cf44..3609bb9  100644 -­‐-­‐-­‐  a/package.json +++  b/package.json @@  -­‐1,5  +1,7  @@  {      "name":  "sample", -­‐    "version":  "0.1.0" +    "version":  "0.1.0", +    "devDependencies":  { +        "mocha":  "~1.4.0", +        "chai":  "~1.2.0" +    }  }
  31. $  mocha  -­‐-­‐reporter  spec  -­‐-­‐slow  5    When  accessing  the

     Ticket  API        with  valid  Morale  credentials,            and  with  a  project  that  exists,                getting  a  list  of  tickets                    ✓  should  return  without  an  error                      ✓  should  return  a  populated  array  (5ms)                    ✓  should  contain  a  task  or  bug  ticket                  adding  a  new  ticket                    ✓  should  return  without  an  error                      ✓  should  return  the  new  ticket  (11ms)  ✔  5  tests  complete  (22ms) $  _
  32. test: @./node_modules/.bin/mocha \ --reporter spec \ --ui tdd watch: @./node_modules/.bin/mocha

    \ --reporter min \ --ui tdd \ --growl \ --watch .PHONY: test watch 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  33. suite('Test  this  thing',  function()  {        test('Do  stuff',

     function()  {                //  Assertion  tests        }); }); describe('Testing  out  this  thing',  function()  {        it('should  do  stuff',  function()  {                //  Assertion  tests        }); }); bddSyntax tddSyntax
  34. test: @./node_modules/.bin/mocha \ --reporter spec \ --ui bdd watch: @./node_modules/.bin/mocha

    \ --reporter min \ --ui bdd \ --growl \ --watch .PHONY: test 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  35. test: @./node_modules/.bin/mocha \ --reporter spec watch: @./node_modules/.bin/mocha \ --reporter min

    \ --growl \ --watch .PHONY: test 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  36. describe('Testing  out  this  thing',  function()  {        before(function(){

                   //  ...        };        describe('with  a  subset  of  that  thing',  function()  {                it('should  do  stuff',  function()  {                        //  Assertion  tests                });                afterEach(function(){                        //  ...                };        }); }); setupTeardown
  37. expectSyntax expect(person).to.be.an('object');        .with.property('age')        .that.is.a('number')

           .that.equals(34); assert.isObject(person); assert.property(person,  "age"); assert.isNumber(person.age); assert.equals(person.age,  34); assertSyntax
  38. expect(person).to.exist        .and.be.an('object')        .with.property('age')  

         .that.is.to.exist        .and.is.a('number')        .and.equals(34);
  39. expect(person).to.exist        .and.be.an('object')        .with.property('age')  

         .that.is.to.exist        .and.is.a('number')        .and.equals(34);
  40. expect(person).to.exist        .and.be.an('object')        .with.property('age')  

         .that.is.to.exist        .and.is.a('number')        .and.equals(34);
  41. expect(person)    .that.is.an('object')    .with.property('address')        .that.is.an('object')  

         .with.property('city')            .that.is.a('string')            .and.equals('Detroit')
  42. person.should.be.an('object')        .with.property('age')        .that.is.a('number')  

         .that.equals(34); shouldSyntax assert.isObject(person); assert.property(person,  "age"); assert.isNumber(person.age); assert.equals(person.age,  34); assertSyntax
  43. var  chai      =  require('chai')    ,  expect  =

     chai.expect    ,  should  =  chai.should();
  44. describe('Testing  out  this  thing',  function()  {        it.skip('should

     be  skipped',  function()  {                //  Assertion  tests        }); }); describe.skip('This  entire  suite  will  be  skipped',  function()  {        it('should  do  stuff',  function()  {                //  Assertion  tests        }); }); skipSyntax
  45. describe('Assertions', function() { describe('of truthiness', function() { it('should pass on

    truthiness', function() { assert.isTrue(true); }); it('should pass on falsiness', function() { assert.isFalse(false); }); }); describe('of type', function() { it.skip('should pass on number', function() { assert.isNumber(5); }); it('should pass on object'); }); }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  46. $  make  test    Assertions        of  truthiness

               ✓  should  pass  on  truthiness              ✓  should  pass  on  falsiness          of  type            -­‐  should  pass  on  number              -­‐  should  pass  on  object    ✔  4  tests  complete  (6ms)    •  2  test  pending $  _
  47. var me = {firstName: 'Jay' , lastName: 'Harris' , getFullName:

    function() { return this.firstName + ' ' + this.lastName; }}; // Returns 'Jay Harris' me.getFullName(); me.getFullName = function() { return 'John Doe'; }; // Returns 'John Doe' me.getFullName(); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  48. var http = require('http'); var reqOptions = { host: 'api.twitter.com',

    path: '/1/statuses/user_timeline.json?' + 'screen_name=jayharris' }; var resCallback = function(res) { var responseData = ''; res.on('data', function(chunk) { responseData += chunk; }); res.on('end', function() { console.log(responseData); }); }; http.request(reqOptions, resCallback).end(); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  49. var nock = require('nock'); var twitter = nock('http://api.twitter.com') .get('/1/statuses/user_timeline.json?'+ 'screen_name=jayharris')

    .reply(200, "This worked"); // Returns "This worked" http.request(reqOptions, resCallback).end(); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  50. // Returns live Twitter data http.request(reqOptions, resCallback).end(); var nock =

    require('nock'); var twitter = nock('http://api.twitter.com') .get('/1/statuses/user_timeline.json?'+ 'screen_name=jayharris') .reply(200, "This worked"); // Returns "This worked" http.request(reqOptions, resCallback).end(); // Returns live Twitter data http.request(reqOptions, resCallback).end(); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18