unit testing express and koa middleware in es2015

49
UNIT TESTING NODE.JS MIDDLEWARE By Morris Singer This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. express and ES15 Edition!

Upload: morris-singer

Post on 15-Jan-2017

1.611 views

Category:

Software


1 download

TRANSCRIPT

Page 1: Unit Testing Express and Koa Middleware in ES2015

UNIT TESTING NODE.JS MIDDLEWARE

By Morris Singer

This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

express and

ES15 Editio

n!

Page 2: Unit Testing Express and Koa Middleware in ES2015

ABOUT ME

• Senior Software Engineer at Verilume

• I Like:

• Test-Driven Development

• Angular 1 and 2, Aurelia, Ionic, and React.js, Node.js, and Cordova

Page 3: Unit Testing Express and Koa Middleware in ES2015

AGENDA• Define middleware and why it isn’t just

a fancy term for controllers or endpoints.

• Review behavior-driven development principles for unit testing.

• Argue why middleware are behavioral units.

• Summarize common challenges testing behavior in Express and Koa.

• Learn and implement a pattern for Express and Koa Middleware.

• Answer questions. (10 minutes)

Page 4: Unit Testing Express and Koa Middleware in ES2015

MIDDLEWAREBuilding Your Product, One Layer at a Time

Page 5: Unit Testing Express and Koa Middleware in ES2015

A SIMPLE CASEOne Middleware Per Endpoint

express

app.use(function (req, res, next) { res.send('hello world'); });

Page 6: Unit Testing Express and Koa Middleware in ES2015

A SIMPLE CASE

“Why is it called ‘Middleware’ anyway?”

app.use(function* (next) { this.body = 'hello world'; });

• One Middleware Per Endpoint

Page 7: Unit Testing Express and Koa Middleware in ES2015

MORE COMPLEX CASESTwo Ways of Stacking Middleware

Variadic Iterative

express

const middleware = [ function (req, res, next) { req.message = 'HELLO WORLD'; next(); }, function (req, res, next) { res.send(req.message.toLowerCase()); } ];

middleware.forEach(app.use);app.use(...middleware);

Page 8: Unit Testing Express and Koa Middleware in ES2015

MORE COMPLEX CASESTwo Ways of Stacking Middleware

Variadic Iterativeapp.use(...middleware);

const middleware = [ function* (next) => { this.message = 'HELLO WORLD'; return yield next; }, function* (next) => { this.body = this.message.toLowerCase(); } ];

middleware.forEach(app.use);

Page 9: Unit Testing Express and Koa Middleware in ES2015

THE MIDDLEWARE STACK

GET

Done

Generate Message

Send Lowercase Message

express

app.use(

function (req, res, next) { req.message = 'HELLO WORLD'; next(); },

function (req, res, next) { res.send(req.message.toLowerCase()); }

);

Page 10: Unit Testing Express and Koa Middleware in ES2015

THE MIDDLEWARE STACK

GET

Done

Generate Message

Assign Lowercase to Body

app.use(

function (next) { this.message = 'HELLO WORLD'; return yield next; },

function (next) { this.body = this.message.toLowerCase(); }

);

Page 11: Unit Testing Express and Koa Middleware in ES2015

A B C

1

D E F

2

G H I

3

GET /

Page 12: Unit Testing Express and Koa Middleware in ES2015

TEST BEHAVIOR

Page 13: Unit Testing Express and Koa Middleware in ES2015

COMMON CHALLENGESOr, Why Node Developers Often Avoid TDD

Page 14: Unit Testing Express and Koa Middleware in ES2015

HTTP RESPONSE TESTS

What happens when we add a middleware to the stack?express

it('should return a 500 error', (done) => { request({ method: 'POST', url: 'http://localhost:3000/api/endpoint' }, (error, response, body) => { Assert.equal(response.statusCode, 500); done(); }); });

Page 15: Unit Testing Express and Koa Middleware in ES2015

TESTING MID-STACK

How do we pull out these anonymous functions?express

const middleware = [ function (req, res, next) { req.message = 'HELLO WORLD'; next(); }, function (req, res, next) { res.send(req.message.toLowerCase()); } ];

middleware.forEach(app.use);

Page 16: Unit Testing Express and Koa Middleware in ES2015

ILLUMINATING TEST FAILURES

What happens if next() is not called?express

import {httpMocks} from 'node-mocks-http';

it('should call next()', (done) => { var req = httpMocks.createRequest(), res = httpMocks.createResponse();

middleware(req, res, () => { done(); }); });

Page 17: Unit Testing Express and Koa Middleware in ES2015

KNOWING WHEN TO TEST

When is the assertion run?express

import {httpMocks} from 'node-mocks-http';

it('should call next()', () => { var req = httpMocks.createRequest(), res = httpMocks.createResponse();

middleware(req, res);

return Assert.equal(req.foo, 'bar'); });

Page 18: Unit Testing Express and Koa Middleware in ES2015

TESTING WITH DATA

Where do data come from?express

app.get('path/to/post', function (req, res, next) { Post.findOne(params).exec(function (err, post) { res.json(post); }); });

Page 19: Unit Testing Express and Koa Middleware in ES2015

DEALING WITH POLLUTION

How does one reset the data?express

it('should update the first post', () => { /* ... */ });

it('should get the first post', () => { /* ... */ });

Page 20: Unit Testing Express and Koa Middleware in ES2015

MOCKING DEPENDENCIES

How does one cut out the external data source?express

app.get('endpoint', function (req, res, next) { request({ method: 'GET', url: 'http://example.com/api/call' }, (error, response, body) => { req.externalData = body; next(); }); });

Page 21: Unit Testing Express and Koa Middleware in ES2015

MIDDLEWARE + SEPARATION OF CONCERNS + FLOW CONTROLThe “Eureka” Moment

Page 22: Unit Testing Express and Koa Middleware in ES2015

OVERVIEW

• Pull behavior into middleware and tests.

• Use promises or generators as flow control.

• Return client-server interaction to endpoint.

• Use promises or generators with Mocha.

Page 23: Unit Testing Express and Koa Middleware in ES2015

PULL BEHAVIOR INTO MIDDLEWARE, TESTS

Endpoint

Test

BehaviorBehavior

BehaviorBehavior

Endpoint

TestTest

Old Paradigm

New Paradigm

Page 24: Unit Testing Express and Koa Middleware in ES2015

PULL BEHAVIOR INTO ENDPOINTS

Old Paradigm New Paradigm

N.B.: This only looks like a lot more code…express

const middleware = [ function (req, res, next) { /* Behavior */ }, function (req, res, next) { /* Behavior */ } ];

app.use(...middleware);

const behavior = { first: function () {}, second: function () {} };

const middleware = [ function (req, res, next) { behavior.first(); next(); }, function (req, res, next) { behavior.second(); next(); } ];

app.use(...middleware);

Page 25: Unit Testing Express and Koa Middleware in ES2015

PULL BEHAVIOR INTO ENDPOINTS

Old Paradigm New Paradigm

const middleware = [ function* (next) { /* Behavior */ }, function* (next) { /* Behavior */ } ];

app.use(...middleware);

const behavior = { first: function* () {}, second: function* () {} };

const middleware = [ function* (next) { yield behavior.first(); return yield next; }, function* (next) { yield behavior.second(); return next; } ];

app.use(...middleware);

Page 26: Unit Testing Express and Koa Middleware in ES2015

USE PROMISES AS FLOW CONTROL

• Clean, standardized interface between asynchronous behavior and endpoints.

• Both endpoints and tests can leverage the same mechanism in the behavior for serializing logic.

express

Page 27: Unit Testing Express and Koa Middleware in ES2015

USE PROMISES AS FLOW CONTROL

Old Paradigm

New Paradigm

express

export function middleware (req, res, next) {

/* Define behavior and call res.json(), next(), etc. */

};

export function behavior () { return new Promise((resolve, reject) => { /* Define behavior and resolve or reject promise. */ }; }

Page 28: Unit Testing Express and Koa Middleware in ES2015

USE GENERATORS (WITH CO) AS FLOW CONTROL

• Same interface between asynchronous behavior and middleware as already used between successive middleware.

• Both endpoints and tests can leverage the same mechanism in the behavior for serializing logic.

Page 29: Unit Testing Express and Koa Middleware in ES2015

CO

Generator based control flow goodness for nodejs and the browser, using promises, letting you write non-blocking code in a nice-ish way.

https://www.npmjs.com/package/co

Page 30: Unit Testing Express and Koa Middleware in ES2015

USE GENERATORS AS LINK BETWEEN MIDDLEWARE AND ENDPOINTS

Old Paradigm

New Paradigm

export function* middleware (next) {

/* Call with assigned context and leverage behavior on the Koa context, yield next, etc.*/

};

export function* behavior () { /* Define behavior and yield values. */ }

Page 31: Unit Testing Express and Koa Middleware in ES2015

RETURN CLIENT-SERVER INTERACTION TO ENDPOINT

Endpoint

Res

Req

Behavior

Res

ReqClient

Endpoint

Value

Object

Behavior

Value

ObjectClient

Old Paradigm

New Paradigm

Page 32: Unit Testing Express and Koa Middleware in ES2015

RETURN CLIENT-SERVER INTERACTION TO ENDPOINTOld Paradigm New Paradigm

express

const middleware = [ function (req, res, next) {}, function (req, res, next) {} ];

app.use(...middleware);

const behavior = [ function () {}, function () {} ];

const middleware = [ function (req, res, next) { behavior[0](/* Pass objects, values */) .then(function () { next(); }) .catch(res.json); }, function (req, res, next) { behavior[1](/* Pass objects, values */) .then(function () { next(); }) .catch(res.json); } ];

app.use(...middleware);

Page 33: Unit Testing Express and Koa Middleware in ES2015

RETURN CLIENT-SERVER INTERACTION TO ENDPOINTOld Paradigm New Paradigm

express

const middleware = [ function (req, res, next) {}, function (req, res, next) {} ];

app.use(...middleware);

const behavior = [ function () {}, function () {} ];

const middleware = behavior.map((func) => { return function (req, res, next) { func() .then(function () { next(); }) .catch(res.json); } };

app.use(...middleware);

Page 34: Unit Testing Express and Koa Middleware in ES2015

RETURN CLIENT-SERVER INTERACTION TO ENDPOINTOld Paradigm New Paradigm

express

const middleware = [ function (req, res, next) {}, function (req, res, next) {} ];

app.use(...middleware);

const behavior = [ function () {}, function () {} ];

const middleware = behavior.map((func) => { return function(args) { return function (req, res, next) { func() .then(function () { next(); }) .catch(res.json); } } };

app.use( middleware[0](/* Pass objects, values */), middleware[1](/* Pass objects, values */) );

Page 35: Unit Testing Express and Koa Middleware in ES2015

RETURN CLIENT-SERVER INTERACTION TO ENDPOINTOld Paradigm New Paradigm

const middleware = [ function* (next) {}, function* (next) {} ];

app.use(...middleware);

const behavior = [ function* () {}, function* () {} ];

const middleware = [ function* (next) { yield behavior[0](/* Pass objects, values */); return yield next; }, function* (next) { yield behavior[1](/* Pass objects, values */); return yield next; } ];

app.use(...middleware);

Page 36: Unit Testing Express and Koa Middleware in ES2015

USING PROMISES WITH MOCHA

We need:

• A test framework syntax that facilitates easy async testing. (Supported natively in Mocha since 1.18.0)

• An assertion syntax that we are familiar with. (Assert)

• A set of assertions that facilitate easily writing tests of promises. (assertPromise)

express

Page 37: Unit Testing Express and Koa Middleware in ES2015

USING PROMISES WITH MOCHA (ASSERT_PROMISE)

Old Paradigm

New Paradigm

express

describe('behavior', () => { it ('resolves under condition X with result Y', (done) => { behavior().then(function (done) { /* Assert here. */ }).finally(done); }); });

import {assertPromise} from 'assert-promise';

describe('behavior', () => { it ('resolves under condition X with result Y', () => { return assertPromise.equal(behavior(), 'value'); }); });

Page 38: Unit Testing Express and Koa Middleware in ES2015

USING GENERATORS WITH MOCHA

We need:

• Use the same async flow that Koa leverages (ES15 generators and co)

• An assertion syntax that we are familiar with. (Assert)

• Mocha syntax that facilitates easily writing tests of generators with co. (co-mocha)

Page 39: Unit Testing Express and Koa Middleware in ES2015

CO-MOCHA

Enable support for generators in Mocha tests using co.

https://www.npmjs.com/package/co-mocha

Page 40: Unit Testing Express and Koa Middleware in ES2015

USING PROMISES WITH MOCHA (CO-MOCHA)

Old Paradigm(No Co-Mocha)

New Paradigm

describe('behavior', () => { it ('resolves under condition X with result Y', (done) => { behavior().then(function () { /* Assert here. */ }).finally(done); }); });

describe('behavior', () => { it ('resolves under condition X with result Y', function* () { return Assert.equal(yield behavior(), 'value'); }); });

Page 41: Unit Testing Express and Koa Middleware in ES2015

PUTTING IT ALL TOGETHER“Detroit Industry” by Diego Rivera

Page 42: Unit Testing Express and Koa Middleware in ES2015

Return Client-ServerInteraction to Endpoints

ENDPOINTS

Pull Behaviorinto Endpoint

import {behavior} from './behavior.js'; app.use(function (req, res, next) { behavior() .then(function () { next(); }) .catch(res.json) });

express

Page 43: Unit Testing Express and Koa Middleware in ES2015

Use Promise as Flow Control

BEHAVIOR

export function behavior (req, res, next) {

return new Promise(function (resolve, reject) { /* Define behavior and resolve or reject. */ }

};

express

Page 44: Unit Testing Express and Koa Middleware in ES2015

Pull Behavior Into Tests

TEST

Use Promises with Mochaimport {assertPromise} from "assert-promise";

var behavior = require('./behavior.js');

describe('behavior', () => { it ('resolves under condition X with result Y', () => { return assertPromise.equal(behavior(), 'value'); }); });

express

Page 45: Unit Testing Express and Koa Middleware in ES2015

Return Client-ServerInteraction to Endpoints

ENDPOINTS

Pull Behaviorinto Endpoint

import {behavior} from './behavior.js'; app.use(function* (next) { let message = yield behavior(); this.body = message; });

Page 46: Unit Testing Express and Koa Middleware in ES2015

Use Generators as Flow Control

BEHAVIOR

export function* behavior (next) {

yield asyncRequest(); return yield next;

};

Page 47: Unit Testing Express and Koa Middleware in ES2015

Pull Behavior Into Tests

TEST

var behavior = require('./behavior.js');

describe('behavior', () => { it ('resolves under condition X with result Y', function* () { return Assert.equal(yield behavior(), 'value'); }); });

Page 48: Unit Testing Express and Koa Middleware in ES2015

QUESTIONS

Page 49: Unit Testing Express and Koa Middleware in ES2015

GET IN TOUCH

! @morrissinger

" linkedin.com/in/morrissinger

# morrissinger.com

$ github.com/morrissinger