Mock require/import modules using cypress - node.js

I'm using sequelize with graphql to connect my postgresql in my react nodejs project. And I'm using Cypress for unit testing. While doing that, I got stuck on how to mock the module import.
describe('db config using sequelize', { tags: ['#unit'] }, () => {
before(() => {
const SequelizeStub = {
authenticate: new Cypress.Promise((resolve) => { resolve(true) }),
DataTypes: cy.stub().returns({}),
}
cy.stub('sequelize').returns(SequelizeStub)
})
it('db config authenticate', async () => {
const { connect } = require('#db/common/dbconfig')
assert.isBoolean(connect.authenticate())
})
})
The #db/common/dbconfig file calls the require('sequelize') and creating the object for sequelize and using the Sequelize object I'm connecting to Postgresql.
So while writing the unit test case coverage for dbconfig file, I would like to mock the require('sequelize') itself instead of the module gets loaded for testing.
So I wrote a stub and replacing it with 'sequelize' assuming that it will mock. But not sure this is the right approach. While running it, I'm getting the following error.
TypeError: Cannot read properties of undefined (reading 'value')
Because this error occurred during a before all hook we are skipping the remaining tests in the current suite: db config using sequelize
Although you have test retries enabled, we do not retry tests when before all or after all hooks fail
Can someone help me with this stub mocking?

Learnt that without importing the actual model, it is not possible to mock it.
So I updated the code as follows,
const SEQUELIZE_NAMESPACE = {
Sequelize: require('sequelize'),
}
const mSequalize = {
authenticate: cy.stub().callsFake(
(params, callback) => {
return new Cypress.Promise((resolve) => resolve())
}),
define: cy.stub().callsFake(
(params, callback) => {
return {}
}),
}
cy.stub(SEQUELIZE_NAMESPACE, 'Sequelize').callsFake((params, callback) => mSequalize)
Used the same const SEQUELIZE_NAMESPACE in source file as well as follows,
const SEQUELIZE_NAMESPACE = {
Sequelize: require('sequelize'),
}
And then created object as follows,
const sequelize = new SEQUELIZE_NAMESPACE.Sequelize(...)
and then it worked. Thanks for one of my peer (#rsmuthu) who helped me fix this.

Related

How can I either wait or prevent jest from running my db connection in Sequelize, so as to stop the error "Cannot log after tests are done..."

Im trying to add unit tests to my node express app. The app uses sequelize with sqlite as its orm/database. I am trying to unit test one of my simplest controllers directly (not even trying to test routes with supertest yet)
I was able to get one basic successful test, the issue I a having is that since my controller imports my sequelize User model, it also imports the instantiation of Sequelize. This results in sequelize trying to connect to the DB while the tests are performed. The test is executed before all the db stuff happens, and hence at the end my result is a lot of "Cannot log after tests are done" because Sequelize is still trying to connect and log things, some from the sequelize module itself and others from my code connecting and sync()ing the db.
If I remove the import and usage of the model in my controller, I no longer have those issues, so clearly importing the model is causing all those calls to initialize sequelize. How can i ask Jest to either wait until all async processes are done, or just prevent sequelize from initializing at all (I dont actually need a db connection, i will test them all with mocks)
So here is what the test looks like
describe('testing user registration', () => {
let mRes: Response<any, Record<string, any>>;
let mNext;
beforeAll(()=>{
mRes = {
status: jest.fn().mockReturnThis(),
json: jest.fn()
}
mNext = jest.fn(err => err)
jest.spyOn(UserModel, 'create').mockResolvedValue(createdUser)
})
afterAll(()=>{
jest.resetAllMocks();
})
test('with invalid email', async () => {
const result = await registerUser(mReqInvalidEmail, mRes, mNext);
expect(mNext).toBeCalledWith(invalidBodyError)
})
})
Here is what the Controller looks like:
const registerUser = async(req:Request,res:Response,next:NextFunction) : Promise<Promise<Response> | void> => {
const {body} = req
const {email, role} = body;
if(!isValidEmail(email) || !isValidRole(role)){
const error = createError(
400,
'Invalid body, please provide a valid email address a role of oneOf[\"user\",\"super\"]',
{
body: {
email: 'a valid email string',
role: 'string, oneOf [user, super]'
}
}
);
return next(error);
}
const password = generatePassword()
const hash = hashPassword(password)
const user = await User.create({email, role, password:hash}).catch((err: Error) : Error => {
return createError(500, 'woopsie', err)
})
if(user instanceof Error){
return next(user)
}
return res.status(200).json({
email,
role,
password
});
}
The model:
import {sqlzDB} from "../../database/db";
const User = sqlzDB.define('User',
{
...myfields, not including to save space
}, {
options
})
And finally, where sequelize gets initialized (declaring sqlzDB). This is all the code that is running that I need to either wait for it to finish, or just prevent it from getting called at all!
const {Sequelize} = require('sequelize');
export const sqlzDB = new Sequelize({
dialect: 'sqlite',
storage: 'database/db.sqlite'
});
sqlzDB.authenticate()
.then(():void => {
console.log('Connection to database established successfully.');
}).catch((err : Error): void => {
console.log('Unable to connect to the database: ', err);
})
sqlzDB.sync().then(()=>{
console.log('Sequelize Synced')
})
My test passes just fine. Note that for the test i wrote i dont actually need the mocks yet, Since im just trying to get the setup to work correctly.
I have tried suggestions I have seen out here like calling await new Promise(setImmediate); after my test, or also closing the connection with sqlzDB.close() which causes some different issues (it closes the connection but still tries to log)
So i dont know how to approach this at this point! Sorry for th elong question and thanks to whoever took their time to read this!
You can try a trick to avoid this issue: Close the connection in afterAll
afterEach(() => { // reset mock should be in afterEach
jest.resetAllMocks();
});
afterAll(async ()=>{
await sqlzDB.close();
await new Promise(res => setTimeout(res, 500)); // avoid jest open handle error
});

Best way to setup files for jest and mongodb

Hihi.
I'm having a problem where jest it's sometimes failing a couple of tests randomly, most of the time because of this error "mongodb memory server cannot perform operation: a background operation is currently running for collection".
In another post I read something about building differents mongo instance for each block of tests.
What I have so far is a globalsetup file where I start the mongo replica set like this:
// global.ts
import { MongoMemoryReplSet } from "mongodb-memory-server";
const replSet = new MongoMemoryReplSet({
replSet: { storageEngine: "wiredTiger" },
});
module.exports = async () => {
await replSet.waitUntilRunning();
const uri = await replSet.getUri();
process.env.MONGODB_URI = uri;
};
and my db.ts is like this
// db.ts
export const connect = async () => {
mongoose.set("useFindAndModify", false);
const conn = mongoose.connect(
process.env.MONGODB_URI || config.connectionString, connectionSettings
);
When trying to call it from a test file I do something like this
// test.spec.ts
import db from "./db";
beforeAll(async () => {
await db.connect();
});
afterAll(async (done) => {
await db.dropCollections();
await db.disconnect(done);
});
beforeEach(async () => {
await seed();
});
describe('Some test', () => {
it('Should not fail and get the seeders', () => {
// some random tests using the seeds values
})
})
What I read in that post is instead of using globalSetup use a setupFile that will run for every test instead of one globally and then I MIGHT be able to solve the concurrency issue I have with my tests.
So, to conclude, does anyone knows if there is a proper way to configure the mongodb in memory or if I am doing something THAT BAD that is allowing this to happend or if is there any kind of improvement I can do that will prevent "mongodb memory server cannot perform operation: a background operation is currently running for collection" this to happen?

How can I use spies with Jest in order to test a NodeJs module/app

I don't understand how to spy whether a method/function has been fired inside a module.
This is my example:
db.js
module.exports.saveUser = (user) => {
console.log('Saving the user', user);
};
app.js
let db = require('./db');
module.exports.handleSignup = (email, password) => {
db.saveUser({ email, password });
}
app.test.js
const db = require('./db');
jest.genMockFromModule('./app');
const app = require('./app');
describe('App: ', () => {
it('should call "db.saveUser" with a user object', () => {
let dbSpy = jest.spyOn(db, 'saveUser');
const user = { email: 'email#email.com', password: 'abc123' };
app.handleSignup(user.email, user.password);
expect(dbSpy).toBeCalled();
});
});
by running this test I can see from the shell the console.log written in saveUser(), so it means that db.saveUser() is firing and Jest is not mocking up saveUser().
What am I doing wrong?
referring to the manual mock example given here:
https://facebook.github.io/jest/docs/en/manual-mocks.html
jest.genMockFromModule() is actually called in a mock module file. Even so, if that db file you're mocking is one of your own local files, you wouldn't need to call genMockFromModule. I believe that that's only used for the npm modules in your node_modules folder and core modules (e.g. 'fs', 'readline', etc.)
that aside
I've been working with Spock for a while and I've found mocking and observing to be a bit of a trial-and-error process. Sometimes you have to get a bit creative with it as it doesn't quite perform as you might expect.
An easy approach that has worked for me in multiple instances (and, I imagine, will work in your case):
create a mock:
./__mocks__/db.js (I'm assuming db.js is in your project's base dir folder)
module.exports = {
fileSaveObserver: [],
getFileSaveObserver () {
return this.fileSaveObserver;
},
saveUser (obj) {
this.fileSaveObserver.push(obj);
}
}
then in ./__tests__/app.test.js:
jest.mock('./db.js');
const app = require('../app');
describe('App: ', () => {
test('should call "db.saveUser" with a user object', () => {
const user = { email: 'email#email.com', password: 'abc123' };
app.handleSignup(user.email, user.password);
const db = require('./db.js');
const observer = db.getFileSaveObserver()
expect(observer.length).toBe(1)
expect(observer).toEqual(user);
})
if you're having problems with the above, try removing the '.js' file extensions from the jest.mock() statement and the require statement (this has been a gotcha for me on some occasions.
hope that helps!

Stubbing pg-promise using sinon and mocha

Suppose I have a the following module, as database.js
const initOptions = {}
const pgp = require('pg-promise')(initOptions)
const config = require('../../config')
const db = pgp({
host: config.database.host,
port: config.database.port,
database: config.database.database,
user: config.database.user,
password: config.database.password
})
module.exports = db
And the following module as create.js
const db = require('./database')
function create (name) {
return new Promise((resolve, reject) => {
db.func('create', name)
.then(data => {
return resolve(data)
})
.catch(err => {
return reject(err)
})
})
}
module.exports = create
I'm trying to run a unit test on create.js that will test that db.func is called with 'create' as first argument and 'name' as the second, but without actually needing to set up a database connection (So tests can run offline).
From what I can gather, this is what libraries like sinon.JS can be used for, so I tried creating a test and stubbed the db object.
const chai = require('chai')
const chaiAsPromised = require('chai-as-promised')
chai.use(chaiAsPromised)
const sinon = require('sinon')
const expect = chai.expect
const create = require('./create.js')
describe('Test Module', () => {
it('should test stuff', () => {
const db = require('./database')
const dbStub = sinon.stub(db, 'func').callsFake(Promise.resolve(null))
expect(create('test').then).to.be.a('Function')
})
})
However, it fails with
TypeError: Cannot redefine property: func
Most likely due to my limited exposure to sinon...
How do I go about stubbing (or maybe I need to mock?) the db function so that I can test it an ensure db.func was called?
You can make the properties configurable by disabling locks with the no noLocking option in Initialization Options. This allows sinon to replace the properties:
const initOptions = { noLocking : true };
On a related note:
Your create function is creating an unnecessary promise wrapper, which is a promise anti-pattern. You should just return the result from db.func, which is a promise already:
function create(name) {
return db.func('create', name);
}
Also callsFake takes a function and you are giving it a promise. Use returns instead:
const dbStub = sinon.stub(db, 'func').returns(Promise.resolve(null))
I'm having trouble setting the noLocking option. The docs state it can be set after initialization, however if I set it with db.$config.options.noLocking = true, the same error occurs. However, if I set it in the database.js init options it works fine.
From the author of pg-promise...
It is because at that point the noLocking can only affect tasks and transactions. And since the db level of the protocol is initiated only once, setting noLocking after the library's initialization doesn't effect it.
I have just updated the documentation to clarify it:
This option is dynamic (can be set before or after initialization). However, changing it after the library's initialization will not affect Database objects that have already been created.

mocha wait for db connection before running tests

Basically to run the tests, I need the database connection to MongoDb to be ready. The database manager has a connect method to get a connection and a get method to get a reference to that connection. The connect method is supposed to be called only once, at startup. This design works fine when running the app because the app starts only if the connect method has been called and returned a connection.
The database manager using mongo native (no mongoskin, no mongoose) (database.js):
var config = require('../config')
var MongoClient = require('mongodb').MongoClient
var connection = null
module.exports.connect = function () {
return MongoClient.connect(config.mongodb.uri + config.mongodb.db)
.then(function (db) {
connection = db
})
}
module.exports.get = function () {
if (!connection) {
throw new Error('Call connect first!')
}
return connection
}
Start the app:
var db = require('./services/database');
db.connect()
.then(function() {
logger.info("mongodb is running");
require('./main')
});
In the application, there are service modules and repository modules. The service modules require repository modules when they have to use the database. I have a BaseRepository module where I define the common queries findOne, findAll, etc. For all the MongoDb collections I have a specific repository that inherits from the BaseRepository. In the constructor, I set the collection by calling the database connection.
It means that when a service module which has a dependency on a repository module, the database connect method has to be called. This is the issue because I don't know how to start all the tests after the call to that connect method. I am open to suggestions if you think it's a design flow.
A test:
var usersService = require('../../services/rest/UsersService');
describe('IT Test', function () {
})
The service:
var usersRepository = require('../dao/UsersRepository');
The repository:
var db = require('../database');
var BaseRepository = require('./BaseRepository');
var util = require('util');
util.inherits(UsersRepository, BaseRepository);
function UsersRepository() {
this.collection = db.get().collection('users'); // <====== The connect method has not been called yet
}
The base repository:
function BaseRepository() {
this.collection = undefined;
}
BaseRepository.prototype.findOne = function (filters) {
return this.collection.findOne(filters)
}
I ended up writing a wrapper for all the tests
'use strict'
var logger = require('../services/logger')()
var db = require('../services/database')
describe('All tests wrapper', function () {
it('should pass', function () {
return db.connect()
.then(function () {
logger.info('MongoDB is running')
require('./agenda/AgendaJobsIT')
require('./dashboard/DashboardsServiceIT')
require('./twitter/TwitterServiceTest')
require('./authentication/TokenServiceTest')
})
})
})
The best way is mocha --delayed switch. mocha doc says
If you need to perform asynchronous operations before any of your suites are run (e.g., for dynamically generating tests), you may delay the root suite. Run mocha with the --delay flag.
For example, use mocha in this way mocha --recursive --exit --delay --ui tdd tests.js and --delayed enable you to trigger running the root suite via calling run() explicitly.
const fn = async x => {
return new Promise(resolve => {
setTimeout(resolve, 1500, 2 * x);
});
};
(async function() {
const z = await fn(3);
suite("won't run until run() executes", () => {})
run();
})();
For more information, please read https://mochajs.org/#delayed-root-suite.

Resources