I have following function called on specific route and I am trying to test if the mongoose method inside is called with specific parameter.
My code:
import boom from 'boom'
import User from '../models/model.user'
export const getSingle = async (req, res, next) => {
try {
const user = await User.findById(req.payload.id, '-auth')
if (user) {
return res.json({user})
}
return next(boom.notFound('User not found'))
} catch (err) {
return next(boom.badImplementation('Something went wrong', err))
}
}
My test case:
process.env.NODE_ENV = 'test'
import 'babel-polyfill'
import mongoose from 'mongoose'
import sinon from 'sinon'
require('sinon-mongoose')
import { getSingle } from '../src/controllers/controller.user'
const User = mongoose.model('User')
describe('User Controller ----> getSingle', () => {
it('Should call findById on User model with user id', async () => {
const req = {
payload: {
id: '123465798'
}
}
const res = { json: function(){} }
const next = function() {}
const UserMock = sinon.mock(User)
UserMock.expects("findById").once().withExactArgs('123465798', '-auth')
await getSingle(req, res, next)
UserMock.verify()
})
})
It fails the test as the method wasn't called even though it was.
Import the same model in the test as is used in the code under test i.e ../models/model.user and it should work as expected.
Related
Where am i going wrong here?
Using mocha, chai, sinon and proxyquire for an express server and sequelize ORM linked with a postgres database
I am trying to test a login controller route from my express server
Before I show the file which I want to run my test on here is what "../services/authService.js" file looks like
../services/authService
const UserService = require("./userService");
module.exports = class AuthService extends UserService {
};
// so UserService will have the method findByEmail
// UserService class looks like this and it is coming from another file require("./userService.js) as stated above
/*
class UserService {
async findByEmail(email) {
try {
const user = await User.findOne({ where: { email: email }});
if (user) {
return user;
}
throw new Error("User not found");
} catch (err) {
err.code = 404;
throw err
}
}
}
*/
And here is the auth-controller.js file which I want to run the test on
auth-controller.js
const bcrypt = require('bcryptjs');
const AuthService = require("../services/authService"); // is a class which extends from another calls see the code above
const authService = new AuthService();
const jwtGenerator = require('../utils/jwtGenerator');
const createError = require("http-errors");
exports.loginRoute = async (req, res, next) => {
try {
req.body.password = String(req.body.password);
// db query trying to force a sinon.stub to resolve a fake value. But code wont pass here hence 500 error
const userQuery = await authService.findByEmail(req.body.email);
const compare = await bcrypt.compare(req.body.password, userQuery.password);
if (!compare) throw createError(401, 'Incorrect password.');
const user = {
id: userQuery.id, role: userQuery.is_admin ? "Administrator" : "User", email: userQuery.email, Authorized: true
}
const token = jwtGenerator(user);
return res
.cookie("access_token", token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
}).status(200).json({ message: "Logged in successfully 😊 👌", user, token });
} catch (error) {
next(error);
}
}
This code works in production but I cannot seem to test it. I used proxyquire to require the modules that the function uses. I have a big problem in making proxyquire work when it comes to my class AuthService here is my test file. As proxyquire is not working with classes some how. proxyquire is not using make AuthServiceMock at all cant figure out why.
First of these are my helper variables which I will use in the test file
../test-utils/user-helper
const createAccessToken = (payload) => jwt.sign(payload, TOKEN, {expiresIn: "1h"});
let loginDetail = {
email: "admin#test.com",
password: "123456"
};
let loginAdminUser = {
id: 1,
email: "admin#test.com",
password: "123456",
is_admin: true
}
const loginUser = {
id: 1,
email: "admin#test.com",
password: "123456",
is_admin: true
}
const adminUser = {
id: 1,
email: 'admin#test.com',
password: '123456',
is_admin: true,
first_name: 'john',
last_name: 'doe',
created_at: "2020-06-26T09:31:36.630Z",
updated_at: "2020-06-26T09:31:49.627Z"
}
module.exports = {
createAccessToken,
loginDetail,
loginAdminUser,
loginUser,
adminUser
}
And here is the test file I placed comments espcially around proxyquire when I am trying to use it as this is giving me some issues when it comes to using it with classes. And as well it is not calling mocked/stubbed npm modules for some reason
auth-controller.spec.js
"use strict";
const _ = require("lodash");
const path = require("path");
const proxyquire = require("proxyquire").noCallThru().noPreserveCache();
const chai = require("chai");
const { expect } = chai;
const sinon = require("sinon");
const sinonChai = require("sinon-chai");
chai.use(sinonChai);
// const AuthServiceOriginalClass = require("../../services/authService"); If i use this directly in proxyquire it calls the original class
const { createAccessToken, loginDetail, loginAdminUser, loginUser, adminUser } = require("../test-utils/user-helper");
const controllerPath = path.resolve('./controllers/authController.js');
describe("login route", () => {
let proxy, authService, bcryptStub, fakeCallback, fakeReq, fakeRes, fakeNext, resolveFn, token;
let result, bcryptStubbing, response;
class UserServiceMock {
async findByEmail(email) {
try {
if (email) {
return loginAdminUser;
}
} catch (error) {
throw error;
}
}
}
class AuthServiceMock extends UserServiceMock {};
bcryptStub = {
compare: function() { return true }
};
let tokeen = (kk) => {
return createAccessToken(kk);
}
// token = sinon.mock(createAccessToken(loginAdminUser)); // ?? which 1 to use?
token = sinon.spy(createAccessToken); // ?? which 1 to use?
// token = sinon.stub(createAccessToken) ?? which 1 to use?
proxy = proxyquire(controllerPath, {
"../services/authService.js": AuthServiceMock, // seems like this is not called at all
// "../services/authService.js": AuthServiceOriginalClass, // commented out if use this instead it calls the original class instant
"bcryptjs": bcryptStub,
"../utils/jwtGenerator": token,
// "#noCallThru": true // keep on or off?
});
before("Stub my methods", () => {
authService = new AuthServiceMock();
// If I call the entire loginRoute I want this stub authTry to be called inside of it and resolve that object value
authTry = sinon.stub(authService, "findByEmail").withArgs(loginDetail.email).resolves(loginAdminUser);
sinon.stub(bcryptStub, "compare").resolves(true); // force it to return true as that seems to be like the code of authController.js
// sinon.stub(token, "createAccessToken")
});
before("call the function loginRoute", async () => {
// fakeCallback = new Promise((res, rej) => {
// resolveFn = res
// });
fakeReq = {
body: {
email: loginDetail.email,
password: loginDetail.password
}
};
fakeRes = {
cookie: sinon.spy(),
status: sinon.spy(),
json: sinon.spy()
}
fakeNext = sinon.stub();
await proxy.loginRoute(fakeReq, fakeReq, fakeNext).then((_result) => {
result = _result;
});
console.log("result")
console.log(result) // undefined
console.log("result")
});
it("login route test if the stubs are called", async () => {
expect(authService.findByEmail).to.have.been.called // never called
// expect(bcryptStubbing).to.have.been.called // never called
// expect(response.status).to.deep.equal(200); // doesn't work
}).timeout(10000);
after(() => {
sinon.reset()
});
});
Where am i going wrong here in the test?
I am trying to initialize my mongodb in a separate file. I am writing the initialization inside a function and exporting the function. Now, I also want to export the db which is written inside that function.How can I do that?
I want to do something like this.
import { MongoClient } from 'mongodb';
import config from '../../config';
import logger from '../../logger';
const connectDB = async () => {
const client = new MongoClient(config.dbUri, {
userNewUrlParser: true,
useUnifiedTopology: true,
});
try {
await client.connect();
const db = client.db('sample_mflix');
logger.info('Connected to Database');
} catch (e) {
logger.error(e);
} finally {
await client.close();
}
};
export default {
connectDB,
db,
};
If it were me, I'd avoid the default export and do the following:
import { MongoClient } from 'mongodb';
import config from '../../config';
import logger from '../../logger';
export let db;
export const connectDB = async () => {
const client = new MongoClient(config.dbUri, {
userNewUrlParser: true,
useUnifiedTopology: true,
});
try {
await client.connect();
db = client.db('sample_mflix');
logger.info('Connected to Database');
} catch (e) {
logger.error(e);
} finally {
await client.close();
}
};
or if you wanted a nicer error
import { MongoClient } from 'mongodb';
import config from '../../config';
import logger from '../../logger';
let db;
export const getDB = () => {
if (!db) {
throw new Error("DB Not yet connected");
}
return db;
}
export const connectDB = async () => {
const client = new MongoClient(config.dbUri, {
userNewUrlParser: true,
useUnifiedTopology: true,
});
try {
await client.connect();
db = client.db('sample_mflix');
logger.info('Connected to Database');
} catch (e) {
logger.error(e);
} finally {
await client.close();
}
};
FIXED: USER storageEngine: "wiredTiger"
I use Mocha / Chai / Supertest and Mongodb-Memory-Server to test my app. But's I received error: Transaction numbers are only allowed on storage engines that support document-level locking
In real database and test by postman, it's working well.
My code:
In database.js
const mongoose = require('mongoose')
const { MongoMemoryReplSet } = require('mongodb-memory-server')
mongoose.set('useFindAndModify', false);
const connect = async () => {
try {
let url = process.env.MONGO_URL
let options = {
//Something
}
if (process.env.NODE_ENV === 'test') {
const replSet = new MongoMemoryReplSet();
await replSet.waitUntilRunning();
const uri = await replSet.getUri();
await mongoose.connect(uri, options)
//log connected
} else {
await mongoose.connect(url, options)
//log connected
}
} catch (error) {
//error
}
}
I have two model: Company and User. I made a function to add a member to company with used transaction. My code
const addMember = async (req, res, next) => {
const { companyId } = req.params
const { userId } = req.body
const session = await mongoose.startSession()
try {
await session.withTransaction(async () => {
const [company, user] = await Promise.all([
Company.findOneAndUpdate(
//Something
).session(session),
User.findByIdAndUpdate(
//Something
).session(session)
])
//Something if... else
return res.json({
message: `Add member successfully!`,
})
})
} catch (error) {
//error
}
}
Here's router:
router.post('/:companyId/add-member',
authentication.required,
company.addMember
)
Test file:
const expect = require('chai').expect
const request = require('supertest')
const app = require('../app')
describe('POST /company/:companyId/add-member', () => {
it('OK, add member', done => {
request(app).post(`/company/${companyIdEdited}/add-member`)
.set({ "x-access-token": signedUserTokenKey })
.send({userId: memberId})
.then(res => {
console.log(res.body)
expect(res.statusCode).to.equals(200)
done()
})
.catch((error) => done(error))
})
})
And i received error: Transaction numbers are only allowed on storage engines that support document-level locking'
How can I fix this?
Add retryWrites=false to your database uri. Example below:
mongodb://xx:xx#xyz.com:PORT,zz.com:33427/database-name?replicaSet=rs-xx&ssl=true&retryWrites=false
I'm new to redux and pulling out my hair trying to get a basic test to work with redux and moxios.
API is just axios, with some custom headers set.
I get an error on my post method:
TypeError: Cannot read property 'then' of undefined
my method:
const login = ({username, password}) => (dispatch) => {
dispatch(actions.loginRequested());
return API.post(`curavi/v2/authentication`, {username, password})
.then(response => dispatch(actions.loginSuccess(response.data.payload)))
.catch((error) => errorHandler(dispatch, error.response));
};
My Test case:
describe('login', () => {
beforeEach(function () {
// import and pass your custom axios instance to this method
moxios.install(API)
});
afterEach(function () {
// import and pass your custom axios instance to this method
moxios.uninstall(API)
});
test('calls loginSuccess when the response is successful', () => {
const store = mockStore();
const mockData = {
data: { payload: 'yay' }
};
moxios.wait(() => {
const request = API.requests.mostRecent();
request.respondWith({
status: 200,
response: mockData
});
});
const expectededActions = [
{type: types.LOGIN_REQUESTED},
{type: types.LOGIN_SUCCESS, payload: 'yay'}
];
actions.loginRequested.mockReturnValue({type: types.LOGIN_REQUESTED});
actions.loginSuccess.mockReturnValue({type: types.LOGIN_SUCCESS, payload: 'yay'});
actions.loginFail.mockReturnValue({type: types.LOGIN_FAIL, message: 'boo'});
return store.dispatch(operations.login({username: 'theuser', password: 'thepassword'}))
.then(() => {
expect(store.getActions()).toEqual(expectededActions);
expect(API.post).toHaveBeenCalledWith('curavi/v2/authentication',
{username: 'theuser', password: 'thepassword'});
});
})
});
Are you sure you get a TypeError in login as you suggest? It doesn't make sense; you'd get that error if API were not an axios instance, in which case API.post() could return undefined. On the other hand, your test won't work for 2 reasons:
You need to replace API.requests.mostRecent() with moxios.requests.mostRecent().
The function you have inside moxios' await won't execute for 0.5 secs, see here. If the return statement in your test were to be reached before then, your test would simply return a promise. You could do the following instead:
test('...', async () => {
// ...
const result = await store.dispatch(
operations.login({
username: 'theuser',
password: 'thepassword',
})
);
expect(store.getActions()).toEqual(expectededActions);
expect(API.post).toHaveBeenCalledWith(/* ... */);
});
You should also make sure to set up the store correctly:
import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';
const middlewares = [thunk];
const mockStore = configureStore(middlewares);
// use your store inside your tests
const store = mockStore();
How do I prevent Service running ... msg from getting logged first? I would like the messages inside testDBConnection fn. to be logged first instead. When DB is not running I would like the Looks like DB is not running msg to be kept getting logged and once the DB kicks in the DB connection has been established and Service running ... msgs should follow. I tried multiple things, but I was not able to come up with proper code. Thanks for your help.
index.js
import app from './config/express';
import config from './config/config';
import logger from './config/winston';
import { initDbConnection } from './server/db';
app.listen(config.port, () => {
initDbConnection();
logger.info(`Service running and listening on port ${config.port}`);
});
db.js
import knex from 'knex';
import config from '../config/config';
import logger from '../config/winston';
const { db } = config;
let pool;
const testDBConnection = (client) => {
const intervalId = setInterval(async () => {
try {
await client.select(1);
logger.info('DB connection has been established');
clearInterval(intervalId);
} catch (error) {
logger.error('Looks like DB is not running');
}
}, 2000);
};
export const initDbConnection = (mock) => {
if (mock) {
pool = knex({});
} else {
pool = knex({
client: 'pg',
version: '7.4.2',
connection: db,
debug: true
});
testDBConnection(pool);
}
};
export const getDb = () => pool;
You could use async/await for that.
import app from './config/express';
import config from './config/config';
import logger from './config/winston';
import { initDbConnection } from './server/db';
app.listen(config.port, async () => {
await initDbConnection();
logger.info(`Service running and listening on port ${config.port}`);
});
db.js:
import knex from 'knex';
import config from '../config/config';
import logger from '../config/winston';
const { db } = config;
let pool, connected;
const testDBConnection = (client) => {
return new Promise(resolve => {
const intervalId = setInterval(async () => {
try {
await client.select(1);
if (connected) {
return;
}
connected = true;
logger.info('DB connection has been established');
clearInterval(intervalId);
resolve('success');
} catch (error) {
logger.error('Looks like DB is not running');
}
}, 2000);
});
};
export const initDbConnection = (mock) => {
if (mock) {
pool = knex({});
} else {
pool = knex({
client: 'pg',
version: '7.4.2',
connection: db,
debug: true
});
return testDBConnection(pool);
}
};
export const getDb = () => pool;
This way, the logger inside the app.listen cb won't be called until the initDbConnection is resolved. Another way would be to just use the promise then.