Testing Node.js Applications

Testing Node.js Applications

A run through on how to effectively test your Node.js applications

Cbb9cafe03e785d80a9aa973c5e43c44?s=128

Thanasis Polychronakis

June 28, 2017
Tweet

Transcript

  1. Testing Node.js Applications

  2. Who is this presentation for? Early - Mid stage startups

    Personal Projects Boilerplates
  3. Today's Menu 1. Testing tools & libraries 2. How to

    setup your Node.js Application 3. Testing directory structure and overview 4. Walkthrough of setting up test cases
  4. Testing Tools & Libraries Mocha - test runner - Chai

    - BDD Assertion library - Sinon - Spies and Mocks - Faker - generate fake data - GH mochajs.org chaijs.org sinonjs.org Marak/Faker.js These are the tools I use and can talk about, tools can change, patterns remain the same...
  5. Setting up your Node.js App Avoid initializing anything outside of

    functions. Wrap your whole application in a single boot entry point.
  6. Setting up your Node.js App const postgreService = require('./services/postgre.service'); const

    webService = require('./services/web.service'); const app = module.exports = {}; app.init = Promise.method(function () { return Promise.all([ postgreService.init(), // ... any other low-level / datastore service ]) .then(function () { return Promise.all([ webService.init(), // ... other high-level services ]); }); }); // Determine if module was the execution entry point const isStandAlone = require.main === module; if (isStandAlone) { app.init(); } index.js Application Boot Standalone Test Ignition
  7. Tests Directory Structure - /test/ |-- asserts/ |-- e2e/ |--

    fixtures/ |-- lib/ |-- unit/
  8. test/asserts/ Perform standardized tests on your models Tests Directory Structure

    Test expected properties of Object Test expected types of Object Test expected values of Object
  9. test/asserts/ Tests Directory Structure const chai = require('chai'); const expect

    = chai.expect; const itemTests = module.exports = {}; itemTests.runAll = function (item) { itemTests.testProperties(item); itemTests.testTypes(item); itemTests.testValues(item); }; Part 1/2
  10. test/asserts/ Tests Directory Structure itemTests.testProperties = function (item) { expect(item).to.be.an('object');

    expect(item).to.have.keys([ 'id', 'name', ]); }; itemTests.testTypes = function (item) { expect(item.id).to.be.a('string'); expect(item.name).to.be.a('string'); }; itemTests.testValues = function (item) { expect(item.id.length).to.equal(12); expect(item.name).to.match(/^[\w]{3,8}$/); }; Part 2/2
  11. test/fixtures/ Tests Directory Structure Easy creation of input data. Use

    faker for randomizing data. Definitely not idempotent functions.
  12. test/fixtures/ Tests Directory Structure test/fixtures/account.fix.js const faker = require('faker'); const

    phoneFix = require('./phone-numbers.fix'); const accountFix = module.exports = {}; accountFix.minFields = () => ({ name: faker.name.firstName(), phone_number: phoneFix.getUS(), });
  13. test/lib/ Tests Directory Structure Contains master test boot "test.lib.js" (more

    next). Libraries to perform all e2e requests per model. As stand-alone methods. As Mocha Setup Cases.
  14. test/lib/test.lib.js Tests Directory Structure Is included by all tests. Has

    two master methods to boot for e2e and unit. e2e practically boots up your Node.js App. Unit boot prepares anything you need to have ready. Contains any commonly used helpers.
  15. test/lib/account.lib.js Tests Directory Structure Provide methods to create account[s] of

    all types (?). Provide methods to setup mocha with new accounts.
  16. test/lib/account.lib.js Tests Directory Structure const axios = require('axios'); const accountFix

    = require('../fixtures/account.fix'); const accountLib = module.exports = {}; accountLib.setupOne = function() { beforeEach(function () { const accountData = accountFix.one(); return accountLib.create(accountData) .then((accountRecord) => { this.accountOne = accountRecord; }); }); }; accountLib.create = function (accountData) { return axios.post('/account/', accountData) .then(function (res) { return res.data; }); }
  17. Putting it all together

  18. Test Account Creation Putting all together const expect = require('chai').expect;

    const testLib = require('../lib/test.lib'); const accountFix = require('../fixtures/account.fix'); const accountLib = require('../lib/account.lib'); const accountAssert = require('../asserts/account.assert'); describe('Account Create', function () { testLib.init(); describe('Nominal behaviors', function() { it('Should create an account and get expected outcome', function () { const accountData = accountFix.one(); return accountLib.create(accountData) .then((accountRecord) => { accountAssert.runAll(accountRecord); }); }); }); });
  19. Test Account Creation Putting all together Important points: Wrap all

    your test cases in a single "describe" statement, so you can easily skip the whole suite. Wrap your tests in double "describe" statements, you will need it to better setup your cases (more on that later).
  20. Test Account Creation Putting all together const expect = require('chai').expect;

    const testLib = require('../lib/test.lib'); const accountLib = require('../lib/account.lib'); const eventsFix = require('../fix/events.fix'); const eventsLib = require('../lib/events.lib'); const eventsAssert = require('../asserts/events.assert'); describe('Create Event', function () { testLib.init(); describe('Nominal behaviors', function() { accountLib.setupOne(); it.only('Should create an account and get expected outcome', function () { const eventData = eventFix.one(); return eventLib.create(this.accountOne.id, eventData) .then((eventRecord) => { eventAssert.runAll(eventRecord); }); }); }); });
  21. Test Account Creation Putting all together More points: Avoid inlining

    actual XHR calls in tests. Make your helper methods flexible. Reserve that only for very edge cases. Beware of context.
  22. To Summarise 1. Invoke tester library INIT to boot service.

    2. Run all setup instructions to bring service to desired state. 3. Run test case. 4. Optionally run the outcome through your automated asserters. Flow of running tests
  23. Thank you Thanasis Polychronakis @thanpolas https://speakerdeck.com/thanpolas

  24. Questions? Thanasis Polychronakis @thanpolas https://speakerdeck.com/thanpolas