Async testing Koa with Jest

A presentation at Node.js Berlin Meetup in October 2017 in Berlin, Germany by Robin Pokorny

Slide 1

Slide 1

Node.js Meetup Berlin 17 October 2017 @robinpokorny Async testing KOA with JEST

Slide 2

Slide 2

Node.js Meetup Berlin 17 October 2017 @robinpokorny INFO Slides accompany a talk. Here, the talk is missing. I wrote a transcript which can substitute the talk. Async testing KOA Find it on this link: with JEST bit.ly/jest-koa

Slide 3

Slide 3

WHAT IS KOA next generation web framework Express’ spiritual successor using ES2017 async/await (no callback hell, yay!) http://koajs.com/

Slide 4

Slide 4

WHAT IS JEST delightful, zero configuration testing platform Jasmine’s and Expect’s (spiritual) successor first-class mocking, snapshots, async testing http://facebook.github.io/jest/

Slide 5

Slide 5

1 2 Testing MIDDLEWARE Testing API

Slide 6

Slide 6

1 2 Testing MIDDLEWARE Testing API

Slide 7

Slide 7

const greetings = async (ctx, next) !=> { ctx.body = ‘Hello.’ await next() ctx.body += ’ Remember to subscribe.’ } const app = new Koa() app.use(greetings) app.listen(3000)

Slide 8

Slide 8

const greetings = async (ctx, next) !=> { ctx.body = ‘Hello.’ await next() ctx.body += ’ Remember to subscribe.’ } const app = new Koa() app.use(greetings) app.listen(3000)

Slide 9

Slide 9

const greetings = async (ctx, next) !=> { ctx.body = ‘Hello.’ await next() ctx.body += ’ Remember to subscribe.’ } const app = new Koa() app.use(greetings) app.listen(3000)

Slide 10

Slide 10

const greetings = async (ctx, next) !=> { ctx.body = ‘Hello.’ await next() ctx.body += ’ Remember to subscribe.’ } const app = new Koa() app.use(greetings) app.listen(3000)

Slide 11

Slide 11

const greetings = async (ctx, next) !=> { ctx.body = ‘Hello.’ await next() ctx.body += ’ Remember to subscribe.’ } const app = new Koa() app.use(greetings) app.listen(3000)

Slide 12

Slide 12

Slide 13

Slide 13

Slide 14

Slide 14

const greetings = async (ctx, next) !=> { ctx.body = ‘Hello.’ await next() ctx.body += ’ Remember to subscribe.’ } test(‘greetings works’, async () !=> { const ctx = {} await greetings(ctx, () !=> {}) expect(ctx.body).toBe( ‘Hello. Remember to subscribe.’ ) }) SIMPLE TEST

Slide 15

Slide 15

const greetings = async (ctx, next) !=> { ctx.body = ‘Hello.’ await next() ctx.body += ’ Remember to subscribe.’ } test(‘greetings works’, async () !=> { const ctx = {} await greetings(ctx, () !=> {}) expect(ctx.body).toBe( ‘Hello. Remember to subscribe.’ ) }) SIMPLE TEST

Slide 16

Slide 16

const greetings = async (ctx, next) !=> { ctx.body = ‘Hello.’ await next() ctx.body += ’ Remember to subscribe.’ } test(‘greetings works’, async () !=> { const ctx = {} await greetings(ctx, () !=> {}) expect(ctx.body).toBe( ‘Hello. Remember to subscribe.’ ) }) SIMPLE TEST

Slide 17

Slide 17

const greetings = async (ctx, next) !=> { ctx.body = ‘Hello.’ await next() ctx.body += ’ Remember to subscribe.’ } test(‘greetings works’, async () !=> { const ctx = {} await greetings(ctx, () !=> {}) expect(ctx.body).toBe( ‘Hello. Remember to subscribe.’ ) }) SIMPLE TEST

Slide 18

Slide 18

const greetings = async (ctx, next) !=> { ctx.body = ‘Hello.’ await next() ctx.body += ’ Remember to subscribe.’ } test(‘greetings works’, async () !=> { const ctx = {} await greetings(ctx, () !=> {}) expect(ctx.body).toBe( ‘Hello. Remember to subscribe.’ ) }) SIMPLE TEST

Slide 19

Slide 19

const greetings = async (ctx, next) !=> { ctx.body = ‘Hello.’ await next() ctx.body += ’ Remember to subscribe.’ } test(‘greetings works’, async () !=> { const ctx = {} await greetings(ctx, () !=> {}) expect(ctx.body).toBe( ‘Hello. Remember to subscribe.’ ) }) SIMPLE TEST

Slide 20

Slide 20

const greetings = async (ctx, next) !=> { ctx.body = ‘Hello.’ await next() ctx.body += ’ Remember to subscribe.’ } test(‘greetings works’, async () !=> { const ctx = {} await greetings(ctx, () !=> {}) expect(ctx.body).toBe( ‘Hello. Remember to subscribe.’ ) }) SIMPLE TEST

Slide 21

Slide 21

const greetings = async (ctx, next) !=> { ctx.body = ‘Hello.’ await next() ctx.body += ’ Remember to subscribe.’ } test(‘greetings works in order’, async () !=> { const ctx = {} const next = jest.fn(() !=> { expect(ctx.body).toBe(‘Hello.’) ctx.body += ’ I am content.’ }) await greetings(ctx, next) expect(next).toHaveBeenCalledTimes(1) expect(ctx.body).toBe( ‘Hello. I am content. Remember to subscribe.’ ) }) BEFOREANDAFTER TEST

Slide 22

Slide 22

const greetings = async (ctx, next) !=> { ctx.body = ‘Hello.’ await next() ctx.body += ’ Remember to subscribe.’ } test(‘greetings works in order’, async () !=> { const ctx = {} const next = jest.fn(() !=> { expect(ctx.body).toBe(‘Hello.’) ctx.body += ’ I am content.’ }) ← before await greetings(ctx, next) expect(next).toHaveBeenCalledTimes(1) expect(ctx.body).toBe( ‘Hello. I am content. Remember to subscribe.’ ) }) BEFOREANDAFTER TEST

Slide 23

Slide 23

const greetings = async (ctx, next) !=> { ctx.body = ‘Hello.’ await next() ctx.body += ’ Remember to subscribe.’ } test(‘greetings works in order’, async () !=> { const ctx = {} const next = jest.fn(() !=> { expect(ctx.body).toBe(‘Hello.’) ctx.body += ’ I am content.’ }) ← before await greetings(ctx, next) expect(next).toHaveBeenCalledTimes(1) expect(ctx.body).toBe( ← after ‘Hello. I am content. Remember to subscribe.’ ) }) BEFOREANDAFTER TEST

Slide 24

Slide 24

const greetings = async (ctx, next) !=> { ctx.body = ‘Hello.’ await next() ctx.body += ’ Remember to subscribe.’ } test(‘greetings works in order’, async () !=> { const ctx = {} const next = jest.fn(() !=> { expect(ctx.body).toBe(‘Hello.’) ctx.body += ’ I am content.’ }) ← before await greetings(ctx, next) expect(next).toHaveBeenCalledTimes(1) expect(ctx.body).toBe( ← after ‘Hello. I am content. Remember to subscribe.’ ) }) BEFOREANDAFTER TEST

Slide 25

Slide 25

const greetings = async (ctx, next) !=> { ctx.body = ‘Hello.’ await next() ctx.body += ’ Remember to subscribe.’ } test(‘greetings works in order’, async () !=> { const ctx = {} const next = jest.fn(() !=> { expect(ctx.body).toBe(‘Hello.’) ctx.body += ’ I am content.’ }) ← before await greetings(ctx, next) expect(next).toHaveBeenCalledTimes(1) expect(ctx.body).toBe( ← after ‘Hello. I am content. Remember to subscribe.’ ) }) BEFOREANDAFTER TEST

Slide 26

Slide 26

test(‘greetings works complete’, async () !=> { const ctx = { response: { set: jest.fn() } !/* ADD OTHER MOCKS !*/ } COMPLETE TEST const next = jest.fn(() !=> { expect(ctx).toMatchSnapshot() }) await expect(greetings(ctx, next)) .resolves.toBeUndefined() expect(next).toHaveBeenCalledTimes(1) expect(ctx).toMatchSnapshot() expect(ctx.response.set.mock.calls).toMatchSnapshot() })

Slide 27

Slide 27

test(‘greetings works complete’, async () !=> { const ctx = { response: { set: jest.fn() } !/* ADD OTHER MOCKS !*/ } COMPLETE TEST const next = jest.fn(() !=> { expect(ctx).toMatchSnapshot() }) await expect(greetings(ctx, next)) .resolves.toBeUndefined() expect(next).toHaveBeenCalledTimes(1) expect(ctx).toMatchSnapshot() expect(ctx.response.set.mock.calls).toMatchSnapshot() })

Slide 28

Slide 28

// Jest Snapshot v1, https://goo.gl/fbAQLP exports[greetings works complete 1] = Object { "body": “Hello.", "response": Object { "set": [Function], }, }; exports[greetings works complete 2] = ; SNAPSHOT

Slide 29

Slide 29

test(‘greetings works complete’, async () !=> { const ctx = { response: { set: jest.fn() } !/* ADD OTHER MOCKS !*/ } COMPLETE TEST const next = jest.fn(() !=> { expect(ctx).toMatchSnapshot() }) await expect(greetings(ctx, next)) .resolves.toBeUndefined() expect(next).toHaveBeenCalledTimes(1) expect(ctx).toMatchSnapshot() expect(ctx.response.set.mock.calls).toMatchSnapshot() })

Slide 30

Slide 30

SNAPSHOT // Jest Snapshot v1, https://goo.gl/fbAQLP exports[greetings works complete 3] = Array [ Array [ "Etag", 1234, ], ];

Slide 31

Slide 31

test(‘greetings works complete’, async () !=> { const ctx = { response: { set: jest.fn() } !/* ADD OTHER MOCKS !*/ } COMPLETE TEST const next = jest.fn(() !=> { expect(ctx).toMatchSnapshot() }) await expect(greetings(ctx, next)) .resolves.toBeUndefined() expect(next).toHaveBeenCalledTimes(1) expect(ctx).toMatchSnapshot() expect(ctx.response.set.mock.calls).toMatchSnapshot() })

Slide 32

Slide 32

.RESOLVES & .REJECTS Better error messages More errors Readable and short

Slide 33

Slide 33

Read error ✖ Expected received Promise to resolve, instead it rejected to value [Error: Read error]

Slide 34

Slide 34

by @kentcdodds

Slide 35

Slide 35

by @kentcdodds

Slide 36

Slide 36

1 2 Testing MIDDLEWARE Testing API

Slide 37

Slide 37

MORE THAN SUM App ≠ compose(app.middleware) Koa wraps the native response and request API testing, HTTP assertions

Slide 38

Slide 38

SUPERTEST HTTP assertions library wrapper over SuperAgent support for Promises https://github.com/visionmedia/supertest

Slide 39

Slide 39

A CLEAR AND CONCISE INTRODUCTION TO TESTING KOA WITH JEST AND SUPERTEST Valentino Gagliardi https://www.valentinog.com/blog/testing-api-koa-jest/

Slide 40

Slide 40

!// server/index.js const app = new Koa() const router = new Router() SAMPLE APP router.get(‘/’, async ctx !=> { ctx.body = { data: ‘Sending some JSON’, person: { name: ‘Ferdinand’, lastname: ‘Vaněk’, role: ‘Brewery worker’, age: 42 } } }) app.use(router.routes()) module.exports = app

Slide 41

Slide 41

app.listen(3000) ✖ app.callback() creates server Supertest will open and close need to close after each test the server for us.

Slide 42

Slide 42

!// test/root.spec.js TEST BOILERPLATE const request = require(‘supertest’) const app = require(‘!../server’) test(‘root route’, async () !=> { const response = await request(app.callback()).get(‘/’); expect(response).toBeDefined() !// @TODO })

Slide 43

Slide 43

!// test/root.spec.js TEST BOILERPLATE const request = require(‘supertest’) const app = require(‘!../server’) test(‘root route’, async () !=> { const response = await request(app.callback()).get(‘/’); expect(response).toBeDefined() !// @TODO })

Slide 44

Slide 44

!// test/root.spec.js TEST BOILERPLATE const request = require(‘supertest’) const app = require(‘!../server’) test(‘root route’, async () !=> { const response = await request(app.callback()).get(‘/’); expect(response).toBeDefined() !// @TODO })

Slide 45

Slide 45

ITEM-LEVEL ASSERTIONS expect(response.status).toEqual(200) expect(response.type).toEqual(‘application/json’) expect(response.body.data).toEqual(‘Sending some JSON’) expect(Object.keys(response.body.person)).toEqual( expect.arrayContaining([‘name’, ‘lastname’, ‘role’, ‘age’]) )

Slide 46

Slide 46

ITEM-LEVEL ASSERTIONS expect(response.status).toEqual(200) expect(response.type).toEqual(‘application/json’) expect(response.body.data).toEqual(‘Sending some JSON’) expect(Object.keys(response.body.person)).toEqual( expect.arrayContaining([‘name’, ‘lastname’, ‘role’, ‘age’]) )

Slide 47

Slide 47

expect(response.body).toEqual( expect.objectContaining({ person: { name: expect.anything(), lastname: expect.any(String), role: expect.stringMatching(/^Brewery/), age: expect.any(Number) } }) ) OBJECT EQUALITY

Slide 48

Slide 48

expect(response.body).toEqual( expect.objectContaining({ person: { name: expect.anything(), lastname: expect.any(String), role: expect.stringMatching(/^Brewery/), age: expect.any(Number) } }) ) expect.objectContaining({ x: 1 }) ✖ { x: 1 } OBJECT EQUALITY

Slide 49

Slide 49

expect(response.body).toMatchSnapshot() SNAPSHOTS !// test/!snapshots!/root.spec.js.snap exports[root route with object equality 1] = Object { "data": "Sending some JSON", "person": Object { "age": 42, "lastname": "Vaněk", "name": "Ferdinand", "role": "Brewery worker", }, };

Slide 50

Slide 50

TDD ✖ SNAPSHOTS algorithms structures write before concurrent or after part whole

Slide 51

Slide 51

NOT ONLY KOA applies to other frameworks API testing - no change convenient for refactoring

Slide 52

Slide 52

RELATED • • A clear and concise introduction to testing Koa with Jest and Supertest An Introduction to Building TDD RESTful APIs with Koa 2, Mocha and Chai • • • • • • both by Valentino Gagliardi API testing with Jest by Koen van Gilst Testing async/await middleware? (GitHub Issue) Async testing in Jest (recording of presentation) Snapshot Testing APIs with Jest by Dave Ceddia Snapshot testing in Jest (recording of presentation)

Slide 53

Slide 53

ARTICLE @robinpokorny bit.ly/jest-koa