Slide 1

Slide 1 text

Testing Node.js Applications

Slide 2

Slide 2 text

Who is this presentation for? Early - Mid stage startups Personal Projects Boilerplates

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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...

Slide 5

Slide 5 text

Setting up your Node.js App Avoid initializing anything outside of functions. Wrap your whole application in a single boot entry point.

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

Tests Directory Structure - /test/ |-- asserts/ |-- e2e/ |-- fixtures/ |-- lib/ |-- unit/

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

test/fixtures/ Tests Directory Structure Easy creation of input data. Use faker for randomizing data. Definitely not idempotent functions.

Slide 12

Slide 12 text

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(), });

Slide 13

Slide 13 text

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.

Slide 14

Slide 14 text

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.

Slide 15

Slide 15 text

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.

Slide 16

Slide 16 text

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; }); }

Slide 17

Slide 17 text

Putting it all together

Slide 18

Slide 18 text

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); }); }); }); });

Slide 19

Slide 19 text

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).

Slide 20

Slide 20 text

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); }); }); }); });

Slide 21

Slide 21 text

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.

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

Thank you Thanasis Polychronakis @thanpolas https://speakerdeck.com/thanpolas

Slide 24

Slide 24 text

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