Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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 text

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 text

Testing
 MIDDLEWARE 1 Testing
 API 2

Slide 6

Slide 6 text

Testing
 MIDDLEWARE 1 Testing
 API 2

Slide 7

Slide 7 text

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 text

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 text

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 text

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 text

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 text

No content

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

SIMPLE
 TEST 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.' ) })

Slide 15

Slide 15 text

SIMPLE
 TEST 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.' ) })

Slide 16

Slide 16 text

SIMPLE
 TEST 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.' ) })

Slide 17

Slide 17 text

SIMPLE
 TEST 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.' ) })

Slide 18

Slide 18 text

SIMPLE
 TEST 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.' ) })

Slide 19

Slide 19 text

SIMPLE
 TEST 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.' ) })

Slide 20

Slide 20 text

SIMPLE
 TEST 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.' ) })

Slide 21

Slide 21 text

BEFORE- AND- AFTER TEST 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.' ) })

Slide 22

Slide 22 text

BEFORE- AND- AFTER TEST 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.' ) }) ← before

Slide 23

Slide 23 text

BEFORE- AND- AFTER TEST 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.' ) }) ← before ← after

Slide 24

Slide 24 text

BEFORE- AND- AFTER TEST 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.' ) }) ← before ← after

Slide 25

Slide 25 text

BEFORE- AND- AFTER TEST 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.' ) }) ← before ← after

Slide 26

Slide 26 text

COMPLETE TEST test('greetings works complete', async () => { const ctx = { response: { set: jest.fn() } /* ADD OTHER MOCKS */ } 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 text

COMPLETE TEST test('greetings works complete', async () => { const ctx = { response: { set: jest.fn() } /* ADD OTHER MOCKS */ } 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 text

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

Slide 29

Slide 29 text

COMPLETE TEST test('greetings works complete', async () => { const ctx = { response: { set: jest.fn() } /* ADD OTHER MOCKS */ } 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 text

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

Slide 31

Slide 31 text

COMPLETE TEST test('greetings works complete', async () => { const ctx = { response: { set: jest.fn() } /* ADD OTHER MOCKS */ } 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 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

by @kentcdodds

Slide 35

Slide 35 text

by @kentcdodds

Slide 36

Slide 36 text

Testing
 MIDDLEWARE 1 Testing
 API 2

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

SAMPLE APP // server/index.js const app = new Koa() const router = new Router() 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 text

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

Slide 42

Slide 42 text

TEST BOILERPLATE // test/root.spec.js 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 text

TEST BOILERPLATE // test/root.spec.js 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 text

TEST BOILERPLATE // test/root.spec.js 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 text

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 text

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 text

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

Slide 48

Slide 48 text

OBJECT EQUALITY 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 }

Slide 49

Slide 49 text

SNAPSHOTS expect(response.body).toMatchSnapshot() // 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 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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 text

ARTICLE bit.ly/jest-koa @robinpokorny