We're looking to migrate over to Koa 2. So far i've converted from generators to async await but I'm having a hard time wrapping my head around how to update my unit tests to test context bubbling up. I've scoured your code and other repo's seeing if anyone has good middleware examples but come up short.
I used to test my middleware as such:
const gen = someMiddleware.call(koaContext);
// Here I force bubble up behavior allowing my middleware to add
// or manipulate data on the context.
gen.next().next();
koaContext.state.someValue.should.equal(...)
How do I accomplish the same bubble up behavior using async await?
In the end all a middleware does is mutate the Context. With that in mind testing is as easy as this:
// Custom middleware
const foo = (ctx, next) => {
ctx.state = {
bar: "Hello World!",
};
await next();
}
// Test case
it("should set bar", async () => {
const mock = {
state: undefined,
};
// Simply pass a noop function as `next` argument
const noop = () => { };
await foo(mock, noop);
assert.equal(mock.state.bar, "Hello World!");
});
Exactly what i'm looking for! Thank you @marvinhagemeister
I have an addendum to this: if you want to test what comes "after" the middleware when the server winds down, but "before" it when it winds back up, call it. If you want to test what happens "before" the middleware when it winds back up, await it like the previous comment. However, this only works if you don't have any promises/awaits before the next call. For example, if the middleware is:
async function asyncMw(ctx, next) {
ctx.body = 'Hello world!';
await next();
ctx.body = 'Goodbye, cruel world!';
}
then a test could be:
const noop = () => {
// empty function so some linters don't complain
};
it('says hello at first', () => {
asyncMw(noop);
expect(ctx.body).toBe('Hello world!');
});
it('then it says goodbye', async () => {
await asyncMw(noop);
expect(ctx.body).toBe('Goodbye, cruel world!');
});
I'm still wondering how to test what happens "after" the middleware if you have several awaits and a next, as awaiting would call the next. I also wonder how to test that other middleware will even be called after this one, for example for routers that run one-by-one and don't run if the previous route was matched.
The important thing is that ctx is passed to the middleware, so you can check changes on it in the next you provide. I rewrote the test by @trisys3, enabling what he describes in the last paragraph.
Please note I wrap next in jest.fn so I can check it run and the assertions in it passed. Otherwise they could be skipped by not calling await next(). You can remove it if you test it in a separate test. Alternatively, one could add expect.assertions(2) on top – and keep it updated.
const asyncMw = async (ctx, next) => {
ctx.body = 'Hello world!';
await next();
ctx.body = 'Goodbye, cruel world!';
};
test('asyncMw', async () => {
const ctx = {};
const next = jest.fn(() => {
// test what happened before `await next()`
expect(ctx.body).toBe('Hello world!');
});
await asyncMw(ctx, next);
expect(next).toHaveBeenCalled();
// test what happened after `await next()`
expect(ctx.body).toBe('Goodbye, cruel world!');
});
This issue inspired me to give a talk about testing Koa middleware and later I put it into a form of an article: https://medium.com/@robinpokorny/async-testing-koa-with-jest-1b6e84521b71
Maybe someone, who google this problem, will find it interesting.
I feel honored to be a part of your article, and vindicated that I commented 3 months after this issue was closed. Great article so far, not that I got very far yet.
Just finished your article @robinpokorny, that was great! A couple grammar errors, but otherwise amazing. I'll have to go through some of your more complicated examples, especially the last couple. You really know a lot about koa and how to test it. Your examples should really be added to the README or koajs.com, which have exactly 0 testing examples between them.
Most helpful comment
In the end all a middleware does is mutate the
Context. With that in mind testing is as easy as this: