jest tests are running beforeAll - javascript

Scenario[UPDATED]
I'm trying to connect to mongodb before running test cases and if I'm not wrong I can use beforeAll which is included in Jest where I can connect to my DB before running test cases, I am also testing my REST api with it
Test
const request = require ('supertest');
const app = require ('../../app');
const db = require ('../../db.js');
const url = 'mongodb://localhost:27017';
//UPDATED beforeALL (thanks to #andreas-köberle)
beforeAll ((done) => {
db.connect (url, (err) => {
if (err) {
console.log ('Unable to connect',err)
process.exit(1)
} else {
console.log('success')
}
});
});
test('should response the GET method', async () => {
console.log('DADAD');
const res = await request (app).get ('/expense'); // I've set /expense in app (app.use('/expense,'expenseRoute)
return expect(res.statusCode).toBe (200);
});
afterAll ( () => {
db.close ();
});
DB
const MongoClient = require ('mongodb').MongoClient;
const dbName = 'expenseTest';
let state = {
db: null,
};
exports.connect = (url, done) => {
if (state.db) return done ();
MongoClient.connect (url, (err, client) => {
const db = client.db(dbName);
state.db = db;
done ();
});
};
exports.get = () => {
return state.db;
};
exports.close = done => {
if (state.db) {
state.db.close ((err, res) => {
state.db = null;
done (err);
});
}
};
ROUTE
const express = require ('express')
const router = express.Router ()
const MongoClient = require ('mongodb').MongoClient
const assert = require ('assert')
let db = require ('../db')
/**
* Returns the expense
*/
router.get ('/', (req, res) => {
console.log(db.get());
let expenseCollection = db.get ().collection ('expenseTrack')
expenseCollection.find({}).toArray((err, docs) => {
res.status(200).send(docs)
})
//res.status(200).send('hello')
})
/**
* Stores the expense in db
*/
router.post ('/', (req, res) => {
let expenseCollection = db.get ().collection ('expenseTrack')
expenseCollection.insert (req.body, (err, result) => {
if (err) console.log (err)
else res.status (200).send (result.ops)
})
})
module.exports = router
I have console logs in Test,in my GET route and in beforeAll, here's the output of npm run test
● Console
console.log test/express/startupTest.test.js:18
DADAD
console.log routes/Expense.js:13
null
console.log test/express/startupTest.test.js:11
Succesfully
So It's clear that it's coming in Test first, If I change my endpoint code to this all test case works fine.
/**
* Returns the expense
*/
router.get ('/', (req, res) => {
// console.log(db.get());
// let expenseCollection = db.get ().collection ('expenseTrack')
// expenseCollection.find({}).toArray((err, docs) => {
// res.status(200).send(docs)
// })
res.status(200).send('hello')
})
After updating beforeAll it is now giving me another error/excpetion
Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.

Related

How to stub a dependency in express middleware (express-validator/mocha/chai/sinon/proxyquire)

**Edit: Re-written with a simple example that works first:
So I've got a test file and 2 modules.
moduleA has a dependency, moduleB
// moduleA.js
const ModuleB = require('./moduleB');
function functionA() {
return 20 + ModuleB.functionB();
};
module.exports = { functionA };
// moduleB.js
const functionB = () => {
return 10;
}
module.exports = { functionB }
My test file stubs out functionB (returned from moduleB) using proxyquire:
const sinon = require('sinon');
const proxyquire = require('proxyquire');
describe('Unit Tests', function() {
it('should work', () => {
const mockedFn = sinon.stub();
mockedFn.returns(30);
const copyModuleA = proxyquire('./moduleA', {
'./moduleB': {
functionB: mockedFn
}
});
console.log(copyModuleA.functionA());
})
});
So it outputs 50 (stubbed functionB 30 + functionA 20)
Now I'm trying to take this example into my code:
moduleA in this case is a file called validation.js. It is dependent on moduleB, in this case a sequelize model, Person, with the function I want to mock: findOne
validation.js exports module.exports = { validateLogin };, a function that calls validate, which returns a function that uses Person.findOne()
So in my mind, as with the simple example, I need to create a stub, point to the validation module in proxyquire, and reference the dependency and its findOne function. Like this:
const stubbedFindOne = sinon.stub();
stubbedFindOne.resolves();
validationModule = proxyquire('../../utils/validation', {
'../models/Person': {
findOne: stubbedFindOne
}
});
This should stub Person.findOne in validation.js. But it doesn't seem to. And I have no idea why.
let validationModule;
describe('Unit Tests', () => {
before(() => {
const stubbedFindOne = sinon.stub();
stubbedFindOne.resolves();
validationModule = proxyquire('../../utils/validation', {
'../models/Person': {
findOne: stubbedFindOne
}
});
})
it.only('should return 422 if custom email validation fails', async() => {
const wrongEmailReq = { body: {email: 'nik#hotmail.com'} };
const res = {
statusCode: 500,
status: (code) => {this.statusCode = code; return this},
};
const validationFn = validationModule.validateLogin();
const wrongEmail = await validationFn(wrongEmailReq, res, ()=>{});
expect(wrongEmail.errors[0].msg).to.be.equal('Custom Authorisation Error');
return;
})
And this is my validation.js file:
const Person = require('../models/Person');
// parallel processing
const validate = validations => {
return async (req, res, next) => {
await Promise.all(validations.map(validation => validation.run(req)));
const errors = validationResult(req);
if (errors.isEmpty()) {
return next();
}
const error = new Error();
error.message = process.env.NODE_ENV === 'development'? 'Validation Failed':'Error';
error.statusCode = !errors.isEmpty()? 422:500;
error.errors = errors.array({onlyFirstError: true});
next(error);
return error;
};
};
const validateLogin = () => {
const validations = [
body('email')
.isString()
// snip
.custom(async (value, {req}) => {
try{
const person = await Person.findOne({ where: { email: value } });
if(!person) return Promise.reject('Custom Authorisation Error');
} catch(err) {
throw err;
}
})
.trim(),
];
return validate(validations);
}
module.exports = {
validateLogin
};
So the code in both the small sample and my app is correct, apart from how I stub the function. It shouldn't resolve or reject anything (I tried both out of desperation). It should return null in order to satisfy the conditional rather than jump to the catch block:
try{
const person = await Person.findOne({ where: { email: value } });
if(!person) return Promise.reject('Custom Authorisation Error');
} catch(err) {
throw err;
}
Hope the simple example helps someone else with proxyquire though

Sinon stub out module's function from a middleware

Based on this question, I need to also make a test for a middleware which also uses the db-connection.js file. The middleware file will look like this:
const dbConnection = require('./db-connection.js')
module.exports = function (...args) {
return async function (req, res, next) {
// somethin' somethin' ...
const dbClient = dbConnection.db
const docs = await dbClient.collection('test').find()
if (!docs) {
return next(Boom.forbidden())
}
}
}
, the database connection file do not change, which is:
const MongoClient = require('mongodb').MongoClient
const dbName = 'test'
const url = process.env.MONGO_URL
const client = new MongoClient(url, { useNewUrlParser: true,
useUnifiedTopology: true,
bufferMaxEntries: 0 // dont buffer querys when not connected
})
const init = () => {
return client.connect().then(() => {
logger.info(`mongdb db:${dbName} connected`)
const db = client.db(dbName)
})
}
/**
* #type {Connection}
*/
module.exports = {
init,
client,
get db () {
return client.db(dbName)
}
}
How the middleware works is by passing list of strings (that strings is roles), I have to query to the database and check whether there is a record of each roles. If the record exists, I will return next(), while if the record does not exist, I will return next(Boom.forbidden()) (next function with a 403 status code from Boom module).
Given the details above, how does one make a test to test out the return value of the middleware if the record exists or not? This means I have to assert the next() and next(Boom.forbidden) to be exact.
Based on the answer. You can create stubs for the req, res objects, and next function.
E.g.(Doesn't run, but it should work.)
const sinon = require('sinon');
describe('a', () => {
afterEach(() => {
sinon.restore();
});
it('should find some docs', async () => {
process.env.MONGO_URL = 'mongodb://localhost:27017';
const a = require('./a');
const dbConnection = require('./db-connection.js');
const dbStub = {
collection: sinon.stub().returnsThis(),
find: sinon.stub(),
};
sinon.stub(dbConnection, 'db').get(() => dbStub);
const req = {};
const res = {};
const next = sinon.stub();
const actual = await a()(req, res, next);
sinon.assert.match(actual, true);
sinon.assert.calledWithExactly(dbStub.collection, 'test');
sinon.assert.calledOnce(dbStub.find);
sinon.assert.calledOnce(next);
});
});

Module's function is not being copied

I have defined a function in a module named server.js and want to use it in sign-in.js. server.js exports only one function-emailExists.
module.exports.emailExists = emailExists;
So I import it using:
const server = require("./../server");
and wish to use as
const emailExists = server.emailExists;// but emailExists is undefined
console.log("emailExists is ", typeof emailExists);// result undefined
but
the below is working as expected
console.log("server.emailExists is ", typeof server.emailExists); // returns function
I thought maybe functions are not copied or referenced, but the below code works as i want the above example to work
//this works as expected
function x() {
console.log("from x");
}
const y = x;
console.log("y is", typeof y); // logs y is function
y(); // returns "from x"
I am not sure whether the difference in these two examples is because in first case function is being copied/ referenced from another module and in the second case, function from the same file is bieng copied/ referenced, as few days ago i created new project just to check this and fuction from other module was being copied, i.e. the below was working.
const y= require("./mod2.js").x //x is function in mod2
console.log(typeof y)// returned fucntion
server.js
const express = require("express");
const path = require("path");
const multer = require("multer");
const db = require("./server/database/index.js");
const http = require("http");
const app = express();
const PORT = 3000;
const router = require("./routes/sign-in.js");
app.use(express.static("./"));
app.get("/", (req, res) => {
res.sendFile(path.join(__dirname, "./index.html"));
});
const upload = multer();
let OTP;
app.post("/sendOTP", upload.none(), async (req, res) => {
OTP = Math.ceil(Math.random() * 100000);
http
.get(
`http://myactualdomain.com:portthatiamusing/${OTP}/${req.body.email}`,
async (response) => {
console.log("got response from linux server");
let existingEmail = await emailExists(req.body.email);
if (existingEmail === true) {
res.json(existingEmail);
} else if (existingEmail === false) {
res.json(existingEmail);
} else {
res.json(await emailExists(req.body.email));
}
}
)
.on("error", (err) => {
res.json({ err });
});
});
async function emailExists(email) {
let sqlResponse = await db
.emailExists(email)
.then((data) => {
if (Object.keys(data).length === 0) {
return { exists: false };
} else {
return { exists: true };
}
})
.catch((err) => {
throw new Error("Error which checking if Email exists in MySQL");
});
return JSON.parse(sqlResponse.exists);
}
app.use(
"/",
(req, res, next) => {
req.OTP = OTP;
next();
},
router()
);
app.listen(PORT);
module.exports.emailExists = emailExists;
sign-in.js
const express = require("express");
const router = express.Router();
const multer = require("multer");
const upload = multer();
const db = require("../server/database/index.js");
const server = require("./../server");
const validateOTP = require("./validateOTP.js");
const emailExists = server.emailExists;
module.exports = () => {
router.use("/validateOTP", validateOTP());
router.post("/sign-in", upload.none(), async (req, res) => {
if (await server.emailExists(req.body.email)) {
let correctPassword = await db
.retreivePassword(req.body.email)
.then((password) => {
if (password[0].password === req.body.password) {
res.json({ successful: true });
} else {
res.json({ successful: false });
}
})
.catch((err) => {
res.json(err);
throw "Error checking password from MySql";
});
} else {
res.json({ successful: "email doesn't exist" });
}
//this works as expected
console.log("server.emailExists is ", typeof server.emailExists);
//this does not works as expected. expeted value function, actual value undefined
console.log("emailExists is ", typeof emailExists);
//this works as expected
function x() {
console.log("from x");
}
const y = x;
console.log("y is", typeof y); // logs y is function
y(); // returns "from x"
});
return router;
};

How to Import function in node.js?

I want have 2 functions in report.js file I want to one is report_data which is api controller and second is test I want to use test function in report_data function.
Below is my code of both functions.
var sequelize = require('../config/sequelize');
const Op = sequelize.Sequelize.Op;
var errors = require('../config/errors');
var error = errors.errors;
const helpers = require('../helpers/validations');
const logger = require('../helpers/logger').logger;
const fs = require('fs');
module.exports = {
report_data: async (req, res) => {
if (!req.body.id) {
logger.warn(error.MANDATORY_FIELDS);
return res.status(500).send(error.MANDATORY_FIELDS)
}
sequelize.sequelize.transaction(async (t1) => {
console.log('socket connected')
test(io)
let result = error.OK
logger.info(result);
return res.status(200).send(result)
}).catch(function (err)
logger.warn(err);
console.log(err)
return res.status(500).send(error.SERVER_ERROR)
})
},
test: function (io) {
console.log(io.sockets)
}
};
The easiest would be to declare test as a named global function:
function test(io) {
console.log(io.sockets)
}
module.exports = {
report_data: async (req, res) => {
// now you call call `test()` here
},
test: test,
}

Code not executed after PUT route using Express

I listen to the chat event of the tmijs library, upon the !overlay chat I want to execute some code. What I want to achieve upon getting that message is:
Fetch the user
Check if the user has enough currency
Deduct currency from the user
Trigger a socket event to my react app
Everything seems to work up until the last bullet point. In my terminal it's shown that my user gets currency (called 'kluiten' in my code) deducted, but all the code that comes after it doesn't get executed.
require('dotenv').config();
const PORT = process.env.PORT || 9000;
class TwitchAPI {
constructor({io}) {
this.io = io;
this.client = new tmi.client(options);
this.client.connect();
this.handleOverlayRequest = this.handleOverlayRequest.bind(this);
this.handleChatMessage = this.handleChatMessage.bind(this);
this.client.on('chat', this.handleChatMessage);
}
handleChatMessage (channel, userstate, message) {
if(message === '!overlay') this.handleOverlayRequest(channel, userstate);
}
async handleOverlayRequest (channel, userstate) {
const requiredKluiten = 5;
const rawFoundUser = await fetch(`http://localhost:${PORT}/api/users/${userstate.username}`);
const foundUser = await rawFoundUser.json();
if(foundUser.instakluiten >= requiredKluiten) {
this.client.action(channel, `${userstate[`display-name`]}, you've got enough instakluiten for this.`);
const method = `PUT`;
const payload = { 'requiredKluiten': requiredKluiten };
const body = JSON.stringify(payload);
const headers = { 'Content-Type': `application/json; charset=utf-8` };
const result = await fetch(`http://localhost:${PORT}/api/users/${userstate.username}/decrementKluiten`, { method, body, headers });
console.log(result);
}
}
}
module.exports = TwitchAPI;
I then have an Express router:
const express = require('express');
const userController = require('../controllers/userController');
const router = express.Router();
router.route('/users/:username/decrementKluiten').put(userController.decrementKluiten);
router.route('/users/:username').get(userController.getUser);
router.route('/overview').get(userController.getOverview);
module.exports = router;
which makes sure the currency gets deducted. What I'm stuck on now is that, after all this has happened, I can't execute any code anymore after the fetch. I found though that I could execute code by resolving the promise in my route, but that feels really dirty and messes up my split up files:
router.route('/users/:username/decrementKluiten').put((req, res) => {
userController.decrementKluiten(req, res).then(x => {
console.log(x);
});
});
Is there a way to wait for my PUT to happen and still execute code after it did?
EDIT
userController.js
const {findChattersPerRole, getUserByUsername, decrementKluiten} = require('../actions');
const find = require(`lodash/find`);
const fetch = require(`isomorphic-fetch`);
const parseJSON = response => response.json();
module.exports = {
getUser: (req, res) => {
const username = req.params.username;
findChattersPerRole()
.then(chattersPerRole => {
const wantedUser = find(chattersPerRole, { username });
getUserByUsername(wantedUser.username)
.then(foundUser => {
if (foundUser) {
res.send(foundUser);
} else {
res.send(`No user has been found`);
}
});
});
},
getOverview: (req, res) => {
fetch(`https://tmi.twitch.tv/group/user/instak/chatters`)
.then(parseJSON)
.then(r => {
return res.json(r);
}).catch(err => {
console.log(err);
});
},
decrementKluiten: (req, res) => {
decrementKluiten(req.params.username, req.body.requiredKluiten);
}
}
actions.js
(Because this contains a lot of code I try to only include the relevant parts for this post, the database calls are done using Sequelize.js)
const decrementKluiten = (username, requiredKluiten) => {
return global.db.Viewer.findOne({
where: { username }
}).then(user => {
return user.decrement({ instakluiten: requiredKluiten });
});
};
module.exports = {
decrementKluiten
};
The issue is likely that you don't respond to the HTTP request in your /users/:username/decrementKluiten route. To solve this, change the exported decrementKluiten method in userController.js-file to this:
decrementKluiten: (req, res) => {
decrementKluiten(req.params.username, req.body.requiredKluiten)
.then(() => res.sendStatus(200))
.catch(() => res.sendStatus(500));
}
Some unrelated pointers to make your code a bit more readable, since you already use async functions in some parts of your code, but in other parts you interface directly with Promises.
The exported part of userController.js could utilize async functions:
module.exports = {
getUser: async (req, res) => {
try {
const username = req.params.username;
let chattersPerRole = await findChattersPerRole();
let wantedUser = find(chattersPerRole, { username });
let foundUser = await getUserByUsername(watnerUser.username);
if (foundUser) {
res.status(200).send(foundUser);
} else {
res.status(404).send('No user has been found');
}
} catch (e) {
res.sendStatus(500);
}
},
getOverview: async (req, res) => {
try {
let r = (await fetch('https://tmi.twitch.tv/group/user/instak/chatters')).json();
res.json(r);
} catch (e) {
res.sendStatus(500);
}
},
decrementKluiten: async (req, res) => {
try {
await decrementKluiten(req.params.username, req.body.requiredKluiten);
res.sendStatus(200);
} catch (e) {
res.sendStatus(500);
}
}
}
I've also added error handling in case something goes wrong, the server responds with a 500 Internal Server Error status code.
Judging by these lines in your TwitchAPI class:
const rawFoundUser = await fetch(`http://localhost:${PORT}/api/users/${userstate.username}`);
const foundUser = await rawFoundUser.json();
I assume you've tried to do const foundUser = await fetch('...').json(). This results in an error, but you can call the retuned value's methods and properties on the same line if you wrap the await expression in parentheses, like this:
const foundUser = await (await fetch('...')).json()`
If its methods does not return a Promise (i.e being synchronous), or you want to access a property, you can do:
const something = (await doSomethingAsync()).someMethod()
const somethingElse = (await doSomethingAsync()).property
I also noticed you're using template literals (backticks, `) for most strings without doing any template interpolation, which could simply be replaced with ' (single-quotes) or " (double-quotes).

Categories