I am creating an API with NodeJS and KOA. For testing, I use chai (chai-http) and mocha.
The problem arises when I use const { username } = ctx.state.user
in my controllers to get the username of the user that sent the request. When accessing them with my application (with Flutter) or with Postman, it works, but when running the tests with mocha, i get the error TypeError: Cannot destructure property 'username' of 'undefined' or 'null'
. When debugging the code, i found that ctx
has a key for state
but the value for that key was empty. I tried the methods .set(...)
and .send(...)
but they only modified values inside ctx.request.header
and ctx.request.body
.
So my question is: is it possible to set a value for ctx.state
with chai and if so, how? I would like to put in something like {user: {username: 'chai'}}
.
Here are the 2 main parts, the controller section to be tested and the test method:
async function bar(ctx, next) {
const { username } = ctx.state.user;
const { value } = ctx.request.body;
// do something with value and username
ctx.status = 200;
}
it("With correct gameKey: should return the rating of the game", done => {
chai
.request('http://localhost:3000')
.post('/foo/bar')
.set('content-type', 'application/json')
.send({value: 3});
.end((err, res) => {
// do some tests
done();
});
});
Here is the whole code from the server index and the test file:
const Koa = require('koa');
const Jwt = require('koa-jwt');
const Router = require('koa-router');
const Cors = require('@koa/cors');
const BodyParser = require('koa-bodyparser');
const Helmet = require('koa-helmet');
const app = new Koa();
const router = new Router();
router.post('/foo/bar', bar);
async function bar(ctx, next) {
const { username } = ctx.state.user;
const { value } = ctx.request.body;
// do something with value and username
ctx.status = 200;
}
app.use(Helmet());
app.use(Cors());
app.use(BodyParser({
enableTypes: ['json'],
strict: true,
onerror(err, ctx) {
ctx.throw('Request body could not be parsed', 422);
},
}));
app.use(Jwt({ secret: process.env.SECRET }).unless({
path: [
// Whitelist routes that don't require authentication
/^\/auth/,
],
}));
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(3000, () => console.log(`API server started on localhost:3000`));
const chai = require("chai");
const chaiHttp = require("chai-http");
chai.use(chaiHttp);
const expect = require('chai').expect;
const userCredentials = {
username: 'chai',
password: 'chai'
}
describe("Route: games/", () => {
before(() => {
chai
.request('http://localhost:3000')
.post('/auth')
.set('content-type', 'application/json')
.send(userCredentials)
.end((err, res) => {
expect(res.status).to.be.eql(200);
});
});
describe("Sub-route: GET /", () => {
describe("Sub-route: PUT /:gameKey/rating", () => {
it("With correct gameKey: should return the rating of the game", done => {
chai
.request('http://localhost:3000')
.post('/foo/bar')
.set('content-type', 'application/json')
.send({value: 3});
.end((err, res) => {
expect(res.status).to.be.eql(200);
done();
});
});
});
});
});
I figured it out thanks to the comment of Sandeep Patel. I had to use the middleware, so i saved the token retreived with the request in the before()
method and added it to the other requests with .set("Authorization", "Bearer " + token)
.
The working test then looks like this:
const chai = require("chai");
const chaiHttp = require("chai-http");
chai.use(chaiHttp);
const expect = require('chai').expect;
const userCredentials = {
username: 'chai',
password: 'chai'
}
describe("Route: games/", () => {
var token;
before(() => {
chai
.request('http://localhost:3000')
.post('/auth')
.set('content-type', 'application/json')
.send(userCredentials)
.end((err, res) => {
expect(res.status).to.be.eql(200);
token = res.body.token; // Added this
});
});
describe("Sub-route: GET /", () => {
describe("Sub-route: PUT /:gameKey/rating", () => {
it("With correct gameKey: should return the rating of the game", done => {
chai
.request('http://localhost:3000')
.post('/foo/bar')
.set("Authorization", "Bearer " + token) // Added this
.set('content-type', 'application/json')
.send({value: 3});
.end((err, res) => {
expect(res.status).to.be.eql(200);
done();
});
});
});
});
});