$30 off During Our Annual Pro Sale. View Details »

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. T E S T D R I V E N

    View Slide

  2. Node.js is a platform built on
    Chrome's JavaScript runtime for
    easily building fast, scalable network
    applications.


    What is node.js?

    View Slide

  3. View Slide

  4. basicFundamentals

    View Slide

  5. packageManagement

    View Slide

  6. is not an acronym

    View Slide

  7. is an acronym
    National Association of Pastoral Musicians

    View Slide

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

    View Slide

  9. npm install
    localInstallation

    View Slide

  10. npm install --global
    globalInstallation
    -g or --global

    View Slide

  11. npm install --link
    dualInstallation

    View Slide

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

    View Slide

  13. npm install
    updateDependencies

    View Slide

  14. testingNode.js

    View Slide

  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

    View Slide

  16. assertingCorrectness

    View Slide

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

    View Slide

  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)

    View Slide

  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

    View Slide

  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

    View Slide

  21. $  node  test.js
    assert.js:104
       throw  new  assert.AssertionError({
                   ^
    AssertionError:  false  ==  true
           at  Object.  (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...
    $  _

    View Slide

  22. Chai Assertion Library

    View Slide

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

    View Slide

  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

    View Slide

  25. var  assert  =  require('chai').assert;

    View Slide

  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

    View Slide

  27. $  node  test.js
    expected  'hello'  to  be  a  number
    $  _

    View Slide

  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

    View Slide

  29. testDriven

    View Slide

  30. Exceptions alone are insufficient

    View Slide

  31. $  node  my-­‐test.js
    assert.js:104
       throw  new  assert.AssertionError({
                   ^
    AssertionError:  false  ==  true
           at  Object.  (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...
    $  _

    View Slide

  32. We need a testing framework!

    View Slide

  33. mocha simple, flexible, fun

    View Slide

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

    View Slide

  35. $  npm  install  -­‐g  mocha
    $  mkdir  test
    $  mocha
       
       ✔  0  tests  complete  (1ms)
    $  

    View Slide

  36. var  mocha  =  require('mocha');

    View Slide

  37. suite('Testing  out  this  thing',  function()  {
           test('should  do  stuff',  function()  {
                   //  Assertion  tests
           });
    });
    tddSyntax

    View Slide

  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

    View Slide

  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)
    $  _

    View Slide

  40. groupedTests

    View Slide

  41. suite('Testing  out  this  thing',  function()  {
           suite('with  a  subset  of  this  other  thing',  function()  {
                   test('should  do  stuff',  function()  {
                           //  Assertion  tests
                   });
           });
    });
    groupSyntax

    View Slide

  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

    View Slide

  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)
    $  _

    View Slide

  44. pendingTests

    View Slide

  45. suite('Testing  out  this  thing',  function()  {
           suite('with  a  subset  of  this  other  thing',  function()  {
                   test('should  do  stuff  someday');
           });
    });
    pendingSyntax

    View Slide

  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

    View Slide

  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
    $  _

    View Slide

  48. setupTeardown

    View Slide

  49. setup();
    teardown();

    View Slide

  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

    View Slide

  51. asynchronousTests

    View Slide

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

    View Slide

  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

    View Slide

  54. All Mocha functions accept this callback

    View Slide

  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

    View Slide

  56. simplifyExecution

    View Slide

  57. be nice to yourself:
    $  mocha  -­‐-­‐ui  tdd  -­‐-­‐reporter  spec
    should be simplified to
    $  make  test and $  npm  test

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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)
    $  _

    View Slide

  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)
    $  _

    View Slide

  63. eliminate global dependency
    $  npm  install  mocha  -­‐-­‐link

    View Slide

  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

    View Slide

  65. update dev dependencies
    $  npm  install  mocha  -­‐-­‐save-­‐dev
    $  npm  install  chai    -­‐-­‐save-­‐dev

    View Slide

  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"
    +    }
     }

    View Slide

  67. notificationSystems

    View Slide

  68. mocha --slow
    slowThreshold
    -s or --slow

    View Slide

  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)
    $  _

    View Slide

  70. mocha --watch
    continuousTesting
    -w or --watch

    View Slide

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

    View Slide

  72. Growl

    View Slide

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

    View Slide

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

    View Slide

  75. ⌘S

    View Slide

  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

    View Slide

  77. behaviorDriven

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  81. setupTeardown

    View Slide

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

    View Slide

  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

    View Slide

  84. There is no tdd equivalent to ‘each’

    View Slide

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

    View Slide

  86. expectAssertions

    View Slide

  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

    View Slide

  88. var  expect  =  require('chai').expect;

    View Slide

  89. assertionChains
    for readability

    View Slide

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

    View Slide

  91. .to
    .be
    .been
    .is
    .that
    .and
    .have
    .with
    syntaxSugar
    for readability

    View Slide

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

    View Slide

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

    View Slide

  94. .property
    subjectChange
    from original object

    View Slide

  95. expect(person)
       .that.is.an('object')
       .with.property('address')
           .that.is.an('object')
           .with.property('city')
               .that.is.a('string')
               .and.equals('Detroit')

    View Slide

  96. shouldAssertions

    View Slide

  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

    View Slide

  98. var  should  =  require('chai').should();

    View Slide

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

    View Slide

  100. assertionChains
    same as expect style*
    except for existence

    View Slide

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

    View Slide

  102. skippedTests

    View Slide

  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

    View Slide

  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

    View Slide

  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
    $  _

    View Slide

  106. Skip is available in bdd only

    View Slide

  107. mockingObjects

    View Slide

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

    View Slide

  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

    View Slide

  110. nock HTTP Mocking Library

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  115. nock nock.recorder.rec(  );
    nock.recorder.play(  );

    View Slide

  116. Spies, Stubs, & Mocks
    Sinon.js

    View Slide

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

    View Slide

  118. browserTesting

    View Slide

  119. Fast, headless browser
    Zombie.js

    View Slide

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

    View Slide

  121. loadTesting

    View Slide

  122. Performance Suite
    nodeload

    View Slide

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

    View Slide

  124. activelyPractice

    View Slide

  125. Color your whole life healthier

    View Slide

  126. jay harris
    [email protected]
    #testdrivennode
    @jayharris
    P R E S I D E N T

    View Slide