How to test simple middleware - javascript

I have a 3 middlewares like this:
module.exports = {
validateRequest: function(req, res, next) {
return new Promise((resolve, reject) => {
if(!req.body.title || !req.body.location || !req.body.description || !req.body.author){
Promise.reject('Invalid')
res.status(errCode.invalid_input).json({
message: 'Invalid input'
})
}
})
},
sendEmail: ...,
saveToDatabase: ...
}
I use those in my route like this:
const { validateRequest, sendEmail, saveToDatabase } = require('./create')
...
api.post('/create', validateRequest, sendEmail, saveToDatabase);
It works, but I can't test it. Here's my (failed) attempt:
test('create.validateRequest should throw error if incorrect user inputs', (done) => {
const next = jest.fn();
const req = httpMocks.createRequest({
body: {
title: 'A new world!',
location: '...bunch of talks...',
description: '...'
}
});
const res = httpMocks.createResponse();
expect(validateRequest(req, res, next)).rejects.toEqual('Invalid')
})
Jest outputs this:
Error
Invalid
Question: How can I test this validateRequest middleware?

So firstly, assuming this is Express, there's no reason (or requirement) to return a Promise from your middleware, return values are ignored. Secondly, your current code will actually cause valid requests to hang because you aren't calling next to propagate the request to the next middleware.
Taking this into account, your middleware should look a bit more like
validateRequest: (req, res, next) => {
if (!req.body.title || !req.body.location || !req.body.description || !req.body.author) {
// end the request
res.status(errCode.invalid_input).json({
message: 'Invalid input'
});
} else {
// process the next middleware
next();
}
},
Based on the above, a valid unit test would look like
test('create.validateRequest should throw error if incorrect user inputs', () => {
const next = jest.fn();
const req = httpMocks.createRequest({
body: {
title: 'A new world!',
location: '...bunch of talks...',
description: '...'
}
});
const res = httpMocks.createResponse();
validateRequest(req, res, next);
// validate HTTP result
expect(res.statusCode).toBe(400);
expect(res._isJSON()).toBeTruthy();
// validate message
const json = JSON.parse(res._getData());
expect(json.message).toBe('Invalid input');
})

Related

Node js error, "myFunction" is not a function

So I am using the fcm-node package in order to send notifications from the Express api route to the app using a registration token.
The function is:
const FCM = require('fcm-node');
const serverKey = ...
const fcm = new FCM(serverKey);
function sendNotification(registrationToken, title, body, dataTitle, dataBody) {
const message = {
to: registrationToken,
notification: {
title: title,
body: body
},
data: {
title: dataTitle,
body: dataBody
}
};
fcm.send(message, (err, response) => {
if (err) console.log('Error ', err)
else console.log('response ', response)
});
};
module.exports = {
sendNotification
};
I made sure that if outside the function, the notification system is running. Now In the api endpoint:
const sendNotification = require('../sendNotification');
router.get('/test', async (req, res, next) => {
sendNotification('...', 'hi', 'bye','1', '2');
return res.send(200)
};
I keep on getting the error "sendNotification" is not a function. What is the cause of this?
Expression require('../sendNotification'); is giving you a object (because you exported a object in this file), so extract what you need out.
const { sendNotification } = require('../sendNotification');
try this:
module.exports = sendNotification
and use it like this:
const sendNotification = require('../sendNotification');

express router test with multiple handlers

I am testing my guard middleware, but altough everything seems to be working fine my expect statement fails.
/// auth.test.js
const request = require('supertest');
const express = require('express');
const app = require('../../app');
const authMiddleware = require('./auth.middleware');
const mockRes = () => {
const res = {};
res.status = jest.fn().mockReturnValue(res);
res.sendStatus = jest.fn().mockReturnValue(res);
res.send = jest.fn().mockReturnValue(res);
return res;
};
describe('Authorization', () => {
const guardedRouter = express.Router();
guardedRouter.get(
'/guardedandauthenticated',
[authMiddleware.authenticate, authMiddleware.authorize('admin')],
(req, res, _next) => {
console.log('seems to be working');
res.status(200);
console.log('res is 200000000');
},
);
let accessToken = '';
beforeAll(async () => {
const res = await request(app).post('/auth/login').send({
username: 'admin',
password: 'admin',
});
expect(res.status).toBe(200);
accessToken = res.body.accessToken;
});
it('should allow access to authorized roles', () => {
const response = mockRes();
// #ts-ignore
guardedRouter.handle(
{
headers: { authorization: `Bearer ${accessToken}` },
url: '/guardedandauthenticated',
method: 'GET',
},
response,
);
// THIS EXPECTATION IS FAILED
expect(response.status).toHaveBeenCalledWith(200);
});
});
/// auth.middleware.js
module.exports.authorize = role => {
return async (req, res, next) => {
if (!req.user) {
return res.status(403).send({
message: 'Unauthorized! No token provided!',
});
}
if (req.user.role === undefined) {
const privileges = await userService.getUserPrivileges(req.user.id);
req.user.role = privileges.map(f => f.privilege_name);
}
const userRoles = req.user.role;
const rolesToCheck = Array.isArray(role) ? role : [role];
if (!rolesToCheck.every(r => userRoles.includes(r))) {
return res.status(403).send({
message: `Unauthorized! Required privileges are: ${userRoles.toString()}`,
});
}
return next();
};
};
/// jest outcome
expect(jest.fn()).toHaveBeenCalledWith(...expected)
Expected: 200
Number of calls: 0
I cleaned up the code, my similar assertions are successfull, and the code seems to be working fine, either the way I setup router is incorrect, or, actually I have no clue. Console messages in the router are on the jest output, so it works fine.
Thanks in Advance,
well it turned out to be a jest issue, you need to tell jest that you are done.
it('should allow access to authorized roles', async done => {
const res = { statusCode: 100 };
res.status = function (code) {
res.statusCode = code;
return res;
};
// #ts-ignore
guardedRouter.handle(
{
headers: { authorization: `Bearer ${accessToken}` },
url: '/guardedandauthenticated',
method: 'GET',
},
res,
);
setTimeout(() => {
done();
expect(res.statusCode).toBe(200);
}, 300);
});
so I added a done callback to test case, and checked value after the handler is done. This still does not look like an ideal solution. The thing is that, handle will call 3 functions, one of them is async, I could not get it to report correct without setting a timer. There should be a solution without the timer, can anyone help with that?

Express - 400 bad request on POST and PUT

I'd like some help as I'm new to Node.js and express. I have the following code which I'm testing on Postman
const Joi = require('#hapi/joi');
const bodyParser = require('body-parser');
// Load the express framework
const express = require('express');
const app = express();
app.use(express.json());
app.use(bodyParser.json());
app.use(express.urlencoded({ extended: true }));
// temp array
const courses = [
{id: 1, title: 'Learning Node.js'},
{id: 2, title: 'How to become a full stack dev'},
{id: 3, title: 'Master your Javascript skills'}
];
app.post('/api/courses', (request, response) => {
let error = validateCourse(request.body);
if (error) {
response.status(400).send(error.details[0].message); // *
return;
}
let course = {
id: courses.length + 1,
name: request.body.name
};
// TODO save
console.log('TODO save the record');
response.send(course);
});
app.put('/api/courses/:id', (request, response) => {
let course = courses.find(c => c.id === parseInt(request.params.id));
if(!course) response.status(404).send('Oops!');
let { error } = validateCourse(request.body);
if (error) {
response.status(400).send(error.details[0].message);
return;
}
// TODO save
console.log('TODO save the record');
response.send(course);
});
function validateCourse(course) {
let schema = Joi.object({
name: Joi.string().min(4).required()
});
console.log(course);
return schema.validate(course);
}
Either when I make a PUT or a POST request in which I supply a name (i.e the validation should pass) I get a 400 error.
On POST request I see the following in the console which refers to where my asterisk (*) is
TypeError: Cannot read property '0' of undefined
at /app/index.js:50:48
On PUT request I see the following in the console Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
In both cases the console.log I have in the validateCourse function is showing an empty object {}
This is a screenshot from postman
Any ideas what I'm doing wrong?
👨‍🏫 I've been test this code and I mean you can try this code below 👇:
app.post('/api/courses', (request, response) => {
let { error } = validateCourse(request.body);
if (error) {
response.status(400).send(error.details[0].message); // *
}
let course = {
id: courses.length + 1,
name: request.body.name
};
// TODO save
console.log('TODO save the record');
response.send(course);
});
app.put('/api/courses/:id', (request, response) => {
let course = courses.find(c => c.id === parseInt(request.params.id));
if(!course) response.status(404).send('Oops!');
let { error } = validateCourse(request.body);
if (error) {
response.status(400).send(error.details[0].message);
}
// TODO save
console.log('TODO save the record');
response.send(course);
});
function validateCourse(course) {
let schema = Joi.object({
name: Joi.string().min(4).required()
});
console.log(course);
return schema.validate(course);
}
I hope it's can help you 🙏.
let course = courses.find(c => c.id === parseInt(request.params.id))
if(!course) return response.status(404).send('Oops!');
let { error } = validateCourse(request.body);
if (error) {
return response.status(400).send(error.details[0].message);
}
response.send(course);
This must resolve the error that you are getting on PUT request.

How to return a value (access token) inside a function without promises?

const callback = (req, res) => {
// your application requests refresh and access tokens
// after checking the state parameter
if (state === null || state !== storedState) {
...
} else {
res.clearCookie(stateKey);
var authOptions = {...};
//rq = require-promise
rq.post(authOptions, function(error, response, body) {
if (!error && response.statusCode === 200) {
var access_token = body.access_token,
refresh_token = body.refresh_token;
// we can also pass the token to the browser to make requests from there
res.redirect('/#' +
querystring.stringify({
access_token: access_token,
refresh_token: refresh_token
}));
}
});
}
};
I have this function that I exported as a module so I can use in the main app.js as a middleware function when, after login, spotify redirects to the /callback.
Since I can't make it as a promise and after it retrieve the value with .then, I was wondering how could I get the access_token that will be generated after login in another module.
Thank you in advance.
You can share value between middleware and define it in req. For simpler explain, this is a example :
// the first middleware : checkAuth.js
const jwt = require('jsonwebtoken')
const authValidator = async (req,res,next) => {
// check token as requirement for auth user
let token = req.headers['authorization']
try {
let verifiedUser = await jwt.verify(token, 'yoursecret', (err, decode) => {
if (err) return res.send(err.message)
return decode
})
// assign verifiedUser to req
req.userData = verifiedUser
// passing to next middleware
next()
} catch (err) {
return res.send('no token provided')
}
}
exports.authValidator = authValidator
It's share to another middleware :
// second middleware : checkRole.js
const roleUser = async (req,res,next) => {
try {
// call the req.UserData and assign other value
if (req.UserData) req.userRole = 'Administrator'
// passing to next middleware
next()
} catch (err) {
return res.send('ooppsss')
}
}
exports.roleUser = roleUser
and in this route you can get them all :
const { Router } = require('express')
const router = Router()
const authValidator = require('/checkAuth.js')
const roleUser = require('./checkRole.js')
// arrange every middleware right here
router.get('/myRoute', authValidator, roleUser, async (req,res) => {
try {
// take all from before
return res.json({
...req.userData,
role: req.userRole
})
} catch (err) {
return res.send('oopsss')
}
})

Writing jest test for chained functions in node.js

I have a function i want to test with jest, the function basicly does some token verifying and takes 3 params
this is de code of the function i want to test:
const verifyToken = (req, res, next) => {
// check header or url parameters or post parameters for token
var token = req.headers['x-access-token']
if (!token) return res.status(403).send({ auth: false, message: 'No token provided.' })
// verifies secret and checks expire date
jwt.verify(token, config.secret, (err, decoded) => {
if (err) return res.status(500).send({ auth: false, message: 'Failed to authenticate token.' })
//put user inside req.user to use the user in other routes
User.findById(decoded.id, (err, user) => {
if (err) {
return res.status(500).json({
message: err
})
} else if (!user) {
return res.status(404).json({
message: 'No user found'
})
} else {
req.user = user
}
next()
})
})
}
so i'm writing a first test, which tests if no token is given in de request, that it sends a 403 with a message. following is the test.
const verifyToken = require('../../config/token')
describe('veryfiy token tests', () => {
it('Should give 403 status when no token is present', () => {
let mockReq = {
headers: {}
}
var mockRes = {
status: code => code
send: message => message
}
let nextCalled = false
let next = () => {
nextCalled = true
}
expect(verifyToken(mockReq, mockRes, next)).toBe(403)
})
})
Now the test passes with an error:
TypeError: res.status(...).send is not a function
when i removed .send() from res.status in the code, the test passes.
I have been trying to figure out how to mock both status() and send() on the res object. but have not found a solution yet.
Tnx
I think the problem is that the result of res.status() does not have a function called send().
Try using this:
var mockRes = {
status: code => ({
send: message => ({code, message})
}),
};
You should be able to test with:
var result = verifyToken(mockReq, mockRes, next);
expect(result.code).toBeDefined();
expect(result.code).toBe(403);
PS: Haven't tested the code :)
you can make chained mock class and test, wether functions are executed or not.
here is an example.
class MockResponse {
constructor() {
this.res = {};
}
status = jest
.fn()
.mockReturnThis()
.mockImplementationOnce((code) => {
this.res.code = code;
return this;
});
send = jest
.fn()
.mockReturnThis()
.mockImplementationOnce((message) => {
this.res.message = message;
return this;
});
}
and now use this mock class to test. and check given function has executed with given result or not.
example like
it("should not call next function, and return 401, if token has not been found", async () => {
let res = new MockResponse(); // here i initialised instance of class
let next = jest.fn();
let req = {cookies:""} // header or cookies where you are receiving token here in my case empty.
await authentication(req, res, next); // here i used my mock res class
expect(next).not.toHaveBeenCalled(); // you must check that next will not be called.
expect(res.status).toHaveBeenCalledWith(401);//you can check result of status
expect(res.send).toHaveBeenCalledWith("not authenticated");// send() message in your function
});

Categories

Resources