Testing Node.js Applications
@thanpolas
Who is Thanasis
Professional
Community
Open Source
- Eng Mgr at Waldo
- CTO at QallOut
- CTO at Insight Replay
- Founder
- Over 40 NPM packages
- Contributor to major Node.js packages
- Avid OSS author
- Local Node.js Meetup organizer (Greece)
- skgtech.io founder
- DEVit Conference organizer
- Software Engineer, CTO, founder
- Recently moved to London from Greece
- Available for hire
Who is this presentation for?
- Early - Mid stage startups
- Personal Projects
- Boilerplates
Today's Menu
- Testing tools & libraries
- How to setup your Node.js Application
- Testing directory structure and overview
- Walkthrough of setting up test cases
Testing Tools & Libraries
- Mocha - test runner - mochajs.org
- Chai - BDD Assertion library - chaijs.org
- Sinon - Spies and Mocks - sinonjs.org
- Faker - generate fake data - GH Marak/Faker.js
These are the tools I use and can talk about, tools can change, patterns remain the same...
Setting up your Node.js App
- Avoid initializing anything outside of functions.
- Wrap your whole application in a single boot entry point.
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
Tests Directory Structure
- /test/
|-- asserts/
|-- e2e/
|-- fixtures/
|-- lib/
|-- unit/
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
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
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
test/fixtures/
Tests Directory Structure
- Easy creation of input data.
- Use faker for randomizing data.
- Definitely not idempotent functions.
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(),
});
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.
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.
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.
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;
});
}
Putting it all together
Test Account Creation
Putting it 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);
});
});
});
});
Test Account Creation
Putting it 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).
Test Account Creation
Putting it 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);
});
});
});
});
Test Account Creation
Putting it 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.
To Summarise
- Invoke tester library INIT to boot service.
- Run all setup instructions to bring service to desired state.
- Run test case.
- Optionally run the outcome through your automated asserters.
Flow of running tests
Thank you
Questions?
Testing a Node.js Application
By thanpolas
Testing a Node.js Application
- 992