Chai-http: cannot set value of nodes ctx.state - node.js

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();
});
});
});
});
});

According to KOA documentation ctx.state The recommended namespace for passing information through middleware and to your frontend views.
app.use(function * (next) {
var ctx = this
ctx.request
ctx.response
ctx.body = 'hello'
ctx.state.user = yield User.find(id).fetch()
next()
}
So i think you can set ctx.state in middleware function which you can use in next request handler.

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();
});
});
});
});
});

Related

How to test requests that require the user to be authenticated in Express

How can you test requests that require the user to be authenticated? I'm using local passport.js in my express app. I'm testing using Jest and Supertest. I've looked up countless posts, I tried supertest-session and that doesn't seem to work either. All of these posts are from almost 10 years ago so I'm not sure if they're still valid.
Here is the last thing that I've tried:
const request = require('supertest');
const app = require('../app');
const { pool } = require('../dbConfig');
let testAccount = { email: 'loginTestUser#gmail.com', password: '123456' }
const loginUrl = '/users/login';
const createRoomUrl = '/rooms/create'
const successfulRoomCreateUrl = '/users/dashboard';
const successfulLoginUrl = '/users/dashboard';
const failedRoomCreateUrl = '/';
afterAll(async () => {
pool.end();
});
describe('Room CRUD: Create | POST /rooms/create', () => {
describe('Given the user is Authenticated', () => {
let token;
beforeEach( async () => {
const response = await
request(app).post(loginUrl).type('form').send(testAccount);
token = { access_token: response.body.token }
});
test(`should get statusCode 200 if user is logged in`, async () => {
const createRoomResponse = await request(app).get(createRoomUrl).query(token);
// 302 since the user doesn't stay logged in
expect(createRoomResponse.statusCode).toEqual(200);
});
});
});
Here is what I tried with supertest-session and it also doesn't work:
const session = require('supertest-session');
const myApp = require('../app');
let testAccount = { email: 'loginTestUser#gmail.com', password: '123456' }
var testSession = null;
beforeEach(function () {
testSession = session(myApp);
});
it('should fail accessing a restricted page', function (done) {
testSession.get('/rooms/create')
.expect(302)
.end(done)
});
it('should sign in', function (done) {
testSession.post('/users/login')
.send(testAccount)
.expect(302) // get redirected to dashboard
.end(done);
});
describe('after authenticating session', function () {
var authenticatedSession;
beforeEach(function (done) {
testSession.post('/users/login')
.send(testAccount)
.expect(302) // get redirected to /users/dashboard
.end(function (err) {
if (err) return done(err);
authenticatedSession = testSession;
return done();
});
});
it('should get a restricted page', function (done) {
authenticatedSession.get('/rooms/create')
.expect(200) // <------ still get a 302 (redirect if user !logged
.end(done)
});
});
Turns out all I needed to do was append .type('form') like so:
beforeEach(function (done) {
testSession.post('/users/login').type('form')
.send(testAccount)
.expect(302) // get redirected to /users/dashboard
.end(function (err) {
if (err) return done(err);
authenticatedSession = testSession;
return done();
});
});

Cant clear database after tests are run on Mocha and Chai using mongodb with mongoose in my node.js app

I will include both the files that are needed for the same
the main issue is that after every test I run I want no more entries to be added in my database and it should be cleaned after the test are done
so I used aftereach() but I suppose it is not working because of something wrong I did, can you help me with that.
here is my test.js
process.env.NODE_ENV = 'test';
// const mongoose = require('mongoose');
const chai = require('chai');
const chaiHttp = require('chai-http');
const Task = require('../config/model');
const server = require('../index');
const { expect } = chai;
chai.use(chaiHttp);
describe('Task', (done) => {
afterEach(() => {
Task.crud.drop();
done();
});
});
// Test Get Tasks
describe('/GET tasks', () => {
it('it should GET all the tasks', async () => {
const res = await chai.request(server)
.get('/task');
// .end((err, res) => {
expect(res).to.have.status(200);
// expect(res.body).to.be.a('array');
// done(err);
});
});
describe('/Post tasks', () => {
it('should Post the task', async () => {
const taskPost = {
task: 'run as fast as possible you idiot',
};
const res = await chai.request(server)
.post('/task')
.send(taskPost);
// .end((err, res) => {
expect(res).to.have.status(200);
// done();
});
});
describe('/GET/:ID', () => {
it('should Get the task by ID', async () => {
const tasks = new Task({ task: 'The Lord of the Rings' });
const task = await tasks.save();
const res = await chai.request(server)
.get(`/task/${task.id}`)
.send(task);
// .end((error, res) => {
expect(res).to.have.status(200);
// done();
// });
});
});
describe('/PUT/:ID task', () => {
it('it should UPDATE a task given the id', async () => {
const tasks = new Task({ task: 'The Chronicles of Narnia' });
const task = await tasks.save();
const res = await chai.request(server)
.put(`/task/${task.id}`)
.send({ task: 'The Chronicles of Sarvesh' });
// .end((error, res) => {
expect(res).to.have.status(200);
// });
});
});
and the files that is in /config/model
const mongoose = require('mongoose');
const Schema = new mongoose.Schema({
task: {
type: String,
required: true,
},
});
module.exports = mongoose.model('crud', Schema, 'crud');
I don't think model has any inbuilt drop method. You need to either use db.collection("collectionName").drop() for which you need handle of current db or use deleteMany and delete everything inside the collection. something like this
describe('Task', (done) => {
afterEach(() => {
Task.crud.deleteMany({}, (err, response) => {
done();
});
});
});

How to mock a knex function used in a route

I have this function that configure knex by environment
const knexConnection = () => {
const config = require('./connection')[environment];
return knex(config)
}
I use this function in my route.js
module.exports = (app) => {
app.get("/test", (req,res)=>{
knexConnection().raw("SELECT NOW() as time").then(result => {
const time = _.get(result.rows[0],'time')
res.send(time);
}).catch(err => throw(err))
})
}
my test file for the route.js
const sinon = require("sinon");
const chai = require("chai");
const mock = require('proxyquire')
const httpStatus = require('http-status');
const expect = chai.expect;
const myStub = sandbox.stub().resolves("Query executed")
const route = mock('../routes', {'../../knexConntection':knexConnection : { raw: myStub }}})
route(app)
chai.request(app)
.get('/test')
.set('content-type', 'application/x-www-form-urlencoded')
.end((err, res) => {
if (err) done(err);
expect(myStub).to.have.been.called;
expect(res.status).to.equal(200)
done();
})
When i execute the test file, the knexConnection.raw is stubbed and it shows the current time. and the test fails. it says the stub was never called.
I've been trying for days and it still hasnt work. any idea how to stub knex query?
UPDATE
After struggling with it for hours, I figured the stub get skipped because the app get instantiated before the stub. so the stub never get loaded.
My server structure has this structure.
-- server.js
//...all server stuff
//load all modeles routes using route
route(app)
here is my index.js as I dynamically load all route in server app.
var fs = require("fs");
module.exports = app => {
fs.readdirSync(__dirname).forEach(file => {
if (file == "index.js") return;
const name = file.substr(0, file.indexOf("."));
require("./" + name)(app);
});
};
My mock still is being skipped and app get called first.
You can't change raw as knexConnection is a function not an object.
knexConnection().raw(...).then(...)
That is, it is a function that returns an object which has a raw function on it.
Besides, we might as well stub knexConnection while we're at it. So we would have control over what raw is.
const promise = sinon.stub().resolves("Query executed")
const knexConnection = sinon.stub().returns({
raw: promise
})
Just one more thing, I've used Mocha. And to pass the stub from beforeEach to it, I use this.currentTest (in beforeEach) and this.test (in it). See the comments.
This made my tests passed:
// Import the dependencies for testing
const chai = require('chai');
const chaiHttp = require('chai-http');
const app = require('../server');
const route = require('../route');
const sinon = require("sinon");
const mock = require('proxyquire')
const httpStatus = require('http-status');
const expect = chai.expect;
chai.use(chaiHttp);
chai.should();
describe("test routes", () => {
beforeEach(function() {
const promise = sinon.stub().resolves("Query executed")
// runs before all tests in this block
const knexConnection = sinon.stub().returns({
raw: promise
})
this.currentTest.myStub = promise //so as to access this in 'it' with this.test.myStub
// warning : {'./knex': { knexConnection : knexConnection }} would replace knexConnection in route file
// with an object { knexConnection : knexConnection } causing the test to fail.
// Instead, you should write {'./knex': knexConnection}
const route = mock('../route', {'./knex': knexConnection})
route(app)
});
it("should call myStub", function(done) {
var myStub = this.test.myStub;
chai.request(app)
.get('/test')
.set('content-type', 'application/x-www-form-urlencoded')
.end((err, res) => {
if (err) done(err);
sinon.assert.called(myStub);
done();
})
})
it("should have 'Query executed' as text", function(done) {
var myStub = this.test.myStub;
chai.request(app)
.get('/test')
.set('content-type', 'application/x-www-form-urlencoded')
.end((err, res) => {
if (err) done(err);
sinon.assert.match(res.text, "Query executed")
done();
})
})
it("should have 200 as status", (done) => {
chai.request(app)
.get('/test')
.set('content-type', 'application/x-www-form-urlencoded')
.end((err, res) => {
if (err) done(err);
expect(res.status).to.equal(200)
done();
})
})
})
The route file:
const knexConnection = require('./knex.js');
module.exports = (app) => {
app.get("/test", (req,res)=>{
knexConnection().raw("SELECT NOW() as time").then(result => {
res.send(result);
}).catch(err => { throw(err) })
})
}
If you have any more questions, please do ask.

Supertest: check for something that happened after res.send()

I'm doing a POST to create an item and send the newly created item as response back to the client.
async (req, res, next) => {
const item = await createItem(xx, yy, zz);
res.send(201, item);
}
Now I also want to send out notifications after creating an item but also after responding to the client - to make the request as fast as possible.
async (req, res, next) => {
const item = await createItem(xx, yy, zz);
res.send(201, item);
sendNotification(item);
}
If I want to test this using jest + supertest, this is how it'd look:
test('return 201', () => {
const app = require('./');
return request(app)
.post('/api/items')
.send({})
.expect(201)
.then(response => {
// test something more
});
}
But how could I test if the sendNotification() was called?
Ok, not perfect but working right now:
I added a call to an external method from another package at the end of the async request-handler. I know that you shouldn't add code just for testing purposes but I prefered this to random setTimeouts in my tests.
hooks.js
const deferreds = [];
exports.hookIntoEnd = () => {
const p = new Promise((resolve, reject) => {
deferreds.push({ resolve, reject });
});
return p;
};
exports.triggerEndHook = () => {
if (Array.isArray(deferreds)) {
deferreds.forEach(d => d.resolve());
}
};
handler.js
const { triggerEndHook } = require('./hooks');
async (req, res, next) => {
const item = await createItem(xx, yy, zz);
res.send(201, item);
sendNotification(item);
// this is only here so that we can hook into here from our tests
triggerEndHook();
}
test.js
test('run + test stuff after res.send', async () => {
const server = require('../index');
const { hookIntoEnd } = require('../hooks');
const aws = require('../utils/aws');
const getObjectMetadataSpy = jest
.spyOn(aws, 'getObjectMetadata')
.mockImplementation(() => Promise.resolve({ Metadata: { a: 'b' } }));
const p = hookIntoEnd();
const response = await request(server)
.post('/api/items')
.send({ foo: 'bar' })
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(201);
expect(response.body).toEqual({ id: 1, name: 'test item'});
// test for code that was run after res.send
return p.then(async () => {
console.log('>>>>>>>>>>> triggerEndHook');
expect(getObjectMetadataSpy).toHaveBeenCalledTimes(2);
});
});
You can use mocking in Jest to spy on the sendNotification() function and assert that it has been called. A simple example:
const sendNotification = require('./sendNotification');
const sendNotificationSpy = jest.spyOn(sendNotification);
test('return 201', () => {
const app = require('./');
return request(app)
.post('/api/items')
.send({})
.expect(201)
.then(response => {
// test something more
expect(sendNotificationSpy).toHaveBeenCalled();
});
}
After res.send() is called the program calls someService.method({param1}) function.
Using sinon to spy that service method:
it('test after send', function(done){
const spy = sinon.spy(someService, 'method');
agent
.set('Authorization', token)
.post('/foo')
.expect(200)
.then(() => {
return setTimeout(function() {
// Assert the method was called once
sinon.assert.callCount(spy, 1);
// Assert the method was called with '{param1}' parameter
sinon.assert.calledWith(spy, {param1});
// Test callback!
done();
}, 100);
});
});
- Using setTimeout with the minimus time (ms) as possible to wait for the method to be called.
Recommendations and improvements will be appreciated! (I'm still trying to avoid using arbitrary amount of timeout)

REST API unit test with promise return in nodejs

I have a function in a class which invokes REST api and returns Promise object.I am able to test Promise object bu I am not sure how we can stub or mock rest api call and test.
Token.js
class Token {
getToken(payload) {
let outahToken = new Promise(function (resolve, reject) {
request('hhtps://xyz.com', function (err, res, body) {
if (err) {
reject(err);
} else {
console.log(res);
resolve(body);
}
})
});
return outahToken;
}
}
module.exports = Token;
Token.test.js
'use strict'
const chai = require("chai");
const expect = chai.expect;
const nock = require('nock');
const sinon = require("sinon");
const Token = require('Token');
describe('Get User tests', () => {
let Token;
beforeEach(() => {
outhController = new Token();
sinon.stub(Token, 'getToken').returns(Promise.resolve({
name: "All"
}));
});
it('Outh test', (done) => {
Token.getToken(payload)
.then(response => {
expect(typeof response).to.equal('object');
done();
});
});
});
We can mock request module with proxyquire and check if it is being called with correct argument. We need proxyquire because request module export a function request() which harder to mock with Sinon only.
And because request method is a callback function, we can use Sinon yields to mock it.
const chai = require('chai');
const sinon = require('sinon');
const proxyquire = require('proxyquire');
const expect = chai.expect;
describe('Token test', function() {
let outhController;
let Token;
let requestStub;
beforeEach(() => {
const err = null;
const res = null;
const body = { name: 'All' };
requestStub = sinon.stub().yields(err, res, body); // create sinon for request function and return response body that we want
Token = proxyquire('Token', { request: requestStub }) // replace original request module with our sinon stub
outhController = new Token();
});
it('Outh test', (done) => {
const payload = {};
outhController.getToken(payload)
.then(response => {
sinon.assert.calledWith(requestStub, 'hhtps://xyz.com');
expect(typeof response).to.equal('object');
expect(response.name).to.equal('All');
done();
});
});
});
Hope it helps

Resources