Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Test Drive Node.js

1318668b99b2d5a3900f3f7758763a69?s=47 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
jay@aranasoft.com | www.aranasoft.com
www.twitter.com/jayharris

1318668b99b2d5a3900f3f7758763a69?s=128

Jay Harris

August 29, 2012
Tweet

Transcript

  1. T E S T D R I V E N

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

    easily building fast, scalable network applications. “ ” What is node.js?
  3. None
  4. basicFundamentals

  5. packageManagement

  6. is not an acronym

  7. is an acronym National Association of Pastoral Musicians

  8. github: install: isaacs/npm comes with node

  9. npm install <package> localInstallation

  10. npm install <package> --global globalInstallation -g or --global

  11. npm install <package> --link dualInstallation

  12. npm install <package> --save[-dev|-optional] dependencyReferences --save or --save-dev or --save-optional

  13. npm install updateDependencies

  14. testingNode.js

  15. 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 “
  16. assertingCorrectness

  17. var  assert  =  require('assert');

  18. 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)
  19. 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
  20. 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
  21. $  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... $  _
  22. Chai Assertion Library

  23. github: install: chaijs/chai npm install chai

  24. 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
  25. var  assert  =  require('chai').assert;

  26. 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
  27. $  node  test.js expected  'hello'  to  be  a  number $

     _
  28. 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
  29. testDriven

  30. Exceptions alone are insufficient

  31. $  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... $  _
  32. We need a testing framework!

  33. mocha simple, flexible, fun

  34. mocha github: install: visionmedia/mocha npm install -g mocha

  35. $  npm  install  -­‐g  mocha $  mkdir  test $  mocha

           ✔  0  tests  complete  (1ms) $  
  36. var  mocha  =  require('mocha');

  37. suite('Testing  out  this  thing',  function()  {        test('should

     do  stuff',  function()  {                //  Assertion  tests        }); }); tddSyntax
  38. ./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
  39. $  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) $  _
  40. groupedTests

  41. suite('Testing  out  this  thing',  function()  {        suite('with

     a  subset  of  this  other  thing',  function()  {                test('should  do  stuff',  function()  {                        //  Assertion  tests                });        }); }); groupSyntax
  42. 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
  43. $  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) $  _
  44. pendingTests

  45. suite('Testing  out  this  thing',  function()  {        suite('with

     a  subset  of  this  other  thing',  function()  {                test('should  do  stuff  someday');        }); }); pendingSyntax
  46. 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
  47. $  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 $  _
  48. setupTeardown

  49. setup(); teardown();

  50. 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
  51. asynchronousTests

  52. test('it  should  not  error',  function(done)  {        search.find("Apples",

     done); }); asynchronousSyntax
  53. 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
  54. All Mocha functions accept this callback

  55. 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
  56. simplifyExecution

  57. be nice to yourself: $  mocha  -­‐-­‐ui  tdd  -­‐-­‐reporter  spec

    should be simplified to $  make  test and $  npm  test
  58. ./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
  59. ./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
  60. ./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
  61. $  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) $  _
  62. $  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) $  _
  63. eliminate global dependency $  npm  install  mocha  -­‐-­‐link

  64. test: @./node_modules/.bin/mocha \ --reporter spec \ --ui tdd .PHONY: test

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  65. update dev dependencies $  npm  install  mocha  -­‐-­‐save-­‐dev $  npm

     install  chai    -­‐-­‐save-­‐dev
  66. $  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" +    }  }
  67. notificationSystems

  68. mocha --slow <ms> slowThreshold -s <ms> or --slow <ms>

  69. $  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) $  _
  70. mocha --watch continuousTesting -w or --watch

  71. $  mocha  -­‐-­‐watch  ✔  5  tests  complete  (22ms)  ^  watching

  72. Growl

  73. mac: win: also: apple app store growlForWindows.com growlNotify

  74. mocha --growl growlNotifications -G or --growl

  75. ⌘S

  76. 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
  77. behaviorDriven

  78. 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
  79. 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
  80. 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
  81. setupTeardown

  82. before(); beforeEach(); after(); afterEach();

  83. 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
  84. There is no tdd equivalent to ‘each’

  85. suite(); test(); setup(); teardown(); describe(); it(); before(); after(); beforeEach(); afterEach();

    bddMethods tddMethods
  86. expectAssertions

  87. 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
  88. var  expect  =  require('chai').expect;

  89. assertionChains for readability

  90. expect(person).to.exist        .and.be.an('object')        .with.property('age')  

         .that.is.to.exist        .and.is.a('number')        .and.equals(34);
  91. .to .be .been .is .that .and .have .with syntaxSugar for

    readability
  92. expect(person).to.exist        .and.be.an('object')        .with.property('age')  

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

         .that.is.to.exist        .and.is.a('number')        .and.equals(34);
  94. .property subjectChange from original object

  95. expect(person)    .that.is.an('object')    .with.property('address')        .that.is.an('object')  

         .with.property('city')            .that.is.a('string')            .and.equals('Detroit')
  96. shouldAssertions

  97. 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
  98. var  should  =  require('chai').should();

  99. var  chai      =  require('chai')    ,  expect  =

     chai.expect    ,  should  =  chai.should();
  100. assertionChains same as expect style* except for existence

  101. expect(foo).to.not.exist; expect(bar).to.exist; should.not.exist(foo); should.exist(foo);

  102. skippedTests

  103. 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
  104. 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
  105. $  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 $  _
  106. Skip is available in bdd only

  107. mockingObjects

  108. JavaScript ships with a mocking framework ...it’s called JavaScript

  109. 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
  110. nock HTTP Mocking Library

  111. nock github: install: flatiron/nock npm install nock

  112. 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
  113. 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
  114. // 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
  115. nock nock.recorder.rec(  ); nock.recorder.play(  );

  116. Spies, Stubs, & Mocks Sinon.js

  117. github: install: cjohansen/Sinon.JS npm install sinon Sinon.js

  118. browserTesting

  119. Fast, headless browser Zombie.js

  120. github: install: assaf/zombie npm install zombie Zombie.js

  121. loadTesting

  122. Performance Suite nodeload

  123. github: install: benschmaus/nodeload npm install nodeload nodeload

  124. activelyPractice

  125. Color your whole life healthier

  126. jay harris jay@aranasoft.com #testdrivennode @jayharris P R E S I

    D E N T