Simplify API and middleware tests with lesser-known features This is a transcript of a presentation given at . October Node.js Berlin Meetup What is Koa and what is Jest Let's briefly describe the libraries we will be working with. ( ) is a JavaScript web server framework. It was developed by the people behind a more famous Express as a lightweight and expressive ‘spiritual successor.’ Koa koajs.com It, too, is middleware based. However, it comes bundled with none. Yes, no router, no body parser, and no proxy. Utilising ES2017 functions brings user-friendly functions and end-to-end flow control. (Are you not familiar with ? Read by . We will use them a lot.) That means we can say ‘bye-bye’ to the callback hell, we know from Express. We will be using the second version; in Koa v1 generators provided the flow control. async/await async/await this helpful intro Mostafa Gaafar ( ) is a modern unit testing framework from Facebook. It brought several concepts to mainstream JavaScript testing: zero configuration, first-class mocking, and snapshots. Jest facebook.github.io/jest If we say Koa is a (spiritual) successor of Express, Jest can be described as a (spiritual) successor of Jasmine and Expect (which was later ). donated to Jest includes a better async/Promise support, which–I think–hasn't received the attention it deserves. I hope it is clear, that is what we will use later. The May release Personally, I have much more experience with Jest, which I use daily for testing React-based apps. Koa, on the other hand, is my hobby which I've only used on private projects. Testing a middleware A Koa middleware has–in general–this form: 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) It receives context which includes information about the request and response. Mutating this object is the only way the middleware communicates. There is no return value; if there were it would be ignored. Testing the middleware, therefore, means observing changes on the context. ctx The second argument is the callback that suspends the current middleware and passes control to the next one. This callback must either be awaited or not be invoked at all–turning the middleware order execution. Of course, it may not be called more than once. Read more details in the well-written , from where I borrowed this animation guide Simple test A simple test of the middleware above can look like this: greeting test('greetings works', async () => {const ctx = {} await greetings(ctx, () => {}) expect(ctx.body).toBe('Hello. Remember to subscribe.')}) First, yes you may use in Jest. The framework will wait for all asynchronous operations to finish. async The callback is an empty function–that is the required minimum. It just returns the flow immediately back to our function. next The context object is a mock. We could provide other data like requested URL or headers. When the whole middleware finishes, we run assertions on the context object. As you can see, we cannot distinguish when the changes happened–‘before’ or ‘after’ , we only know the result. However, for many middleware this is enough. Especially those that run something small only before the next middleware, or only after. await next() What if we need more? Before-and-after test Reading from a file, logging time, and generating ETag are some of the examples when it's essential if it's run before or after passing to next middleware. Such test could look like the following: test('greetings works before-and-after', async () => {const ctx = {} const next = jest.fn(() => {expect(ctx.body).toBe('Hello.') // (1)ctx.body += ' I am content.'}) await greetings(ctx, next) expect(next).toHaveBeenCalledTimes(1) expect(ctx.body).toBe( // (2)'Hello. I am content. Remember to subscribe.')}) Whoa, that has grown a bit. But fear not, it's easy. The most important change is inside the callback. It's not a noop anymore. This is the place, marked with (1), where we test how changed ‘before.’ Here, we can also prepare for the ‘after’ part, which is asserted at (2). next ctx Please note, we wrap in so we can check it run and the assertions in it passed. Otherwise, they could be skipped by not calling . You can remove it if you test it in a separate test. Alternatively, we could add on top –and keep it updated. next jest.fn await next() expect.assertions(2) A small warning: I've seen attempts to divide the test for the two parts by calling the middleware without . That is wrong and dangerous as potentially the ‘after’ part may be called, too. Moreover, it would not work if there were asynchronous operations in the ‘before.’ await Complete middleware test The following is an updated example that uses snapshot testing. The added benefit is that it will catch additional changes, you might otherwise miss. It also shows how to test a function call on a utility provided by Koa, in our case. It uses a short version how to test the number of calls and arguments in each call, order sensitive. response.set 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()}) The last enhancement, you can see there, is matcher added in Jest 20. It does two things: checks the middleware does not return anything and provides better messages in case something throws. Compare: .resolves Before: Read error After: Expected received Promise to resolve, instead it rejected to value[Error: Read error] The difference would be even more pronounced when we would expect an error. For more, watch my presentation with examples: . Async testing in Jest What next? The small units are tested. Does that mean we are done? No. I like this GIF: Still love this one. Unit testers be like: “Looks like it’s working” — Kent C. Dodds We need confidence. Confidence our app works for the end user. We build it up when we pretend to use the app. Testing full API The whole app is technically one middleware which is a of all applied middleware, written by us or taken from libraries. It could be tempting to obtain the list with and test it the same way as described above. That would be wrong. composition app.middleware On top of composing middleware, Koa wraps the native response and request objects and does few other things. We want to test the whole app. It's not a unit test, let's call this an API test. Jest cannot do this by itself, for HTTP assertions we will use . Supertest is a small wrapper over . The great benefit is that it supports promises from the box. supertest SuperAgent Sample app and test boilerplate The sample app has been taken (with some modifications) from 's article . Read it! It goes a bit slower and includes a step-by-step guide to get it all running. Valentino Gagliardi A clear and concise introduction to testing Koa with Jest and Supertest Here I will show few other ways to test the app. // server/index.jsconst Koa = require('koa')const Router = require('koa-router')const router = new Router() const app = new Koa() 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 It is important we export the koa server instance before we call . This way, in the test, we have access to the , and we will avoid one of the most common mistakes in API testing: not closing the server and subsequent memory leaks. Supertest will open and the server for us. app.listen(3000) app.callback close A basic boilerplate of a test is pretty straightforward: // 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}) Firstly, we describe the request: specify path, set headers, or append data. The result is a response promise. We await it to get the content. Secondly, we run assertions on the response. In the following we will replace placeholder-like in the boilerplate with something more sophisticated. expect(response).toBeDefined Item-level assertions Of course, is just an other JS object, so we can use the usual assertions. This is what Valentino does: response 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'])) It's great, except the last expect. That one is confusing and doesn't read well. In terms of readability and maintainability, I prefer more expressive syntax which is part of Expect. Object equality expect(response.body).toEqual(expect.objectContaining({person: {name: expect.anything(),lastname: expect.any(String),role: expect.stringMatching(/^Brewery/),age: expect.any(Number)}})) Here, we describe the structure. Think of Flow or PropTypes. Expect has several utilities: , , , , and some . [expect.anything()](http://facebook.github.io/jest/docs/en/expect.html#expectanything) [expect.any(constructor)](http://facebook.github.io/jest/docs/en/expect.html#expectanyconstructor) [expect.stringContaining(string)](http://facebook.github.io/jest/docs/en/expect.html#expectstringcontainingstring) [expect.stringMatching(regexp)](http://facebook.github.io/jest/docs/en/expect.html#expectstringmatchingregexp) others Jest will try to match the whole object. So when we would add, say, field , it would fail. To ignore other properties use as we do with the whole body (remember it also includes field ). nationality expect.objectContaining data Snapshots There is an alternative: Snapshots. As might have noticed, they are opposite to TDD. One cannot reasonably follow TDD and use snapshots. They show their strength when we either gradually build the API up bit-by-bit in the watch mode, or when we add tests for already working servers. Starting with snapshots cannot get any simpler: expect(response.body).toMatchSnapshot() And the snapshot saved: // test/__snapshots__/root.spec.js.snap exports[`root route with snapshots 1`] = `Object {"data": "Sending some JSON","person": Object {"age": 42,"lastname": "Vaněk","name": "Ferdinand","role": "Brewery worker",},}`; I will repeat it: it cannot get any simpler. Yet, it makes sure we return what we need and nothing extra–which is equally important. Not only Koa Of course, everything we've shown applies to other frameworks as well. For example, API testing is the same for Express–how convenient for refactoring! All code is available on GitHub: . robinpokorny/jest-example-koa Please, if you have any comments or suggestions reach to me. I love to solve puzzles, so I'll look at any problem you send me about Jest. Slides: Slides from the presentation in Berlin Related: by A clear and concise introduction to testing Koa with Jest and Supertest Valentino Gagliardi by An Introduction to Building TDD RESTful APIs with Koa 2, Mocha and Chai Valentino Gagliardi by API testing with Jest Koen van Gilst (GitHub Issue) Testing async/await middleware? (recording of presentation) Async testing in Jest by Snapshot Testing APIs with Jest Dave Ceddia (recording of presentation) Snapshot testing in Jest 👏 . If you like this post, please don’t forget to give a below Every clap notification is a motivational boost for me. If you would like to learn more, I recently started a YouTube channel about JavaScript. I post new video every week, so consider subscribing. Be there from the beginning and help me get better. _JavaScript is my passion: I like to write JavaScript, I like to read JavaScript, and I like to talk JavaScript._www.youtube.com/c/robinpokorny Robin Pokorny on YouTube