Mocking auto generated model functions created using sequelize-auto package - node.js

I have created my models using the sequelize-auto package and used them in my controllers
const sequelize = require('../database/db');
var models = require("../models/init-models").initModels(sequelize);
var User = models.User;
const controllerMethod = async (req,res,next) => {
//calls User.findAll() and returns the results
}
I have called the findAll function of User model in one of my controller methods
I want to test my controller method using Jest and want to mock the findAll function to return an empty object.
I have imported my models in the test file and mocked the findAll function as follows,
//inside test case
models.User.findAll = jest.fn().mockImplementation(() => {
return {}
});
const spy = jest.spyOn(models.User, "findAll")
await controllerMethod(req, res,next);
My question is when I run the test case it runs the actual findAll() function inside the controller instead of the mocked findAll()
i.e. findAll() returns actual data instead of {}
Any help would be greatly appreciated
Thanks in advance

Welcome to Stack Overflow. I think the issue with your code is some confusion on how spyOn works. Please see the documentation here specifically the following:
Note: By default, jest.spyOn also calls the spied method. This is different behavior from most other test libraries. If you want to overwrite the original function, you can use jest.spyOn(object, methodName).mockImplementation(() => customImplementation) or object[methodName] = jest.fn(() => customImplementation);
What this is telling you is that spyOn actual calls the original method, not the mock implementation.
The way I would do this (and assuming you do not need to assert on how findAll is called) is not use spyOn at all but create a dummy models that is returned from initModels, which has your dummy findAll method on it. Something like the following:
const mockModels = {
User: {
findAll: jest.fn(() => {})
}
};
// And then in your test - be careful as jest.mock is "hoisted" so you need to make sure mockModels has already been declared and assigned
test('blah', () => {
jest.mock("../models/init-models", () => ({
initModels: jest.fn(() => mockModels,
});
await controllerMethod(req, res, next) // ...etc

Managed to fix my issue until I come across a better solution.
I created a seperate models.js file and exported all my Models via that. Imported Models to my Controllers from the models.js file instead of const sequelize = require('../database/db'); var models = require("../models/init-models").initModels(sequelize);
models.js
const sequelize = require('../database/db');
var models = require("./init-models").initModels(sequelize);
module.exports.User= models.User;
module.exports.Instrument = models.Instrument;
module.exports.sequelize = sequelize; //exported this too since I have used sequelize.fn in some of my controllers
userController.js
//const sequelize = require('../database/db');
//var models = require("../models/init-models").initModels(sequelize);
//var User = models.User;
const {User,sequelize} = require('../service/models'); //imported models this way
const controllerMethod = async (req,res,next) => {
//calls await User.findAll() and returns the results
}
userController.test.js
const {controllerMethod} = require('../../../controllers/user');
const {User,sequelize} = require('../../../service/models');
//inside test case
jest.spyOn(User, "findAll").mockImplementation(() => {return Promise.resolve([])});
await controllerMethod(req, res,next);
in this way findAll mocks the way I wanted and returns the expected []

Related

Calling a stored procedure with sequelize and an express API

This is probably pretty easy but I've been unable to piece it together properly.
I'm trying to use the Sequelize NPM to call a stored procedure that I built and then I want to trigger it with a GET request from from an express api and return the output of the procedure to the api.
Here is what my code looks like for the Sequelize portion....
// Testing stored procedure //
const Retrieve = (testName) => connection.testdata_connection.query("EXEC [SPROC] [INPUTS]")
module.exports = {
tests: Tests(),
retrieve: Retrieve()
};
This part "connection.testdata_connection" is just establishing my connection to the database and I have tested this and I know this part is set.
I would like to be able to hit that with something like...
const query = require('./database/queries'); ///Imports sequelize queries
const app = express();
app.get('/decrypt', function(req,res){
query.retrieve()
})
})
This doesn't work at all.
Now if I do something like this in the queries file...
const Retrieve = async function() {
const decrypt = await connection.testdata_connection.query("EXEC [SPROC] [INPUT]")
console.log(decrypt)
}
module.exports = {
tests: Tests(),
retrieve: Retrieve()
};
This will log to my console with correct data when I start the server. I want it to do that when I hit it with my endpoint.
First, your function should be exported but not executed:
// creating an async function (all i/o operations should be async).
const Retrieve = async(testName) => connection.testdata_connection.query("EXEC [SPROC] [INPUTS]")
module.exports = {
retrieve: Retrieve,
// retrieve: Retrieve() if you call the function with (), the function will be executed and we don't want that yet
};
Now we can call it in the route:
const query = require('./database/queries'); ///Imports sequelize queries
const app = express();
// the route is async because it is executing async code
app.get('/decrypt', async (req,res) => {
// waiting for the answer with await
const response = await query.retrieve();
// Doing something with the response
})
You still need to check for errors, but that is the basics.

How to spy/stub/mock firestore db.collection().add() when db is a custom class property, i.e. how to stub this.db.collection().add()?

I have a class, Firestore, that inits a firebase db in the constructor, this.db, and has an addEntry() method that adds a new entry to the db. How can I stub/mock the write to the db so that no writes are made during testing? The assertion of this test is that db.collection().add() is called once.
firestore.js
class Firestore {
constructor() {
this.db = firestoreAdmin.firestore()
}
async addEntry(newEntry) {
newEntry.claimed = "false"
var collectionReference = await this.db.collection('collection_name').add(newEntry)
return collectionReference._path.segments[1]
}
}
test_firestore.js
const sinon = require('sinon')
const chai = require('chai')
const Firestore = require('../firestore.js')
describe('file firestore.js | class Firestore', function() {
const firestore = new Firestore()
describe('method addEntry(newEntry)', function() {
it('should call this.db.collection().add() once', function() {
var newEntry = {
"client": "clientName"
}
var add = sinon.spy(firestore.db.collection, 'add')
firestore.addEntry(newEntry)
sinon.assert.calledOnce(add)
add.restore()
})
})
})
Right now I'm getting this error:
1 failing
1) file firestore.js | class Firestore
method addEntry(newEntry)
should add key:value pair (claimed: false) prior to writing to db:
TypeError: Attempted to wrap undefined property add as function
Instead of spy sinon doc, consider use stub sinon doc. A spy will wrap the original function and does exactly what the original function does, in your case, write to the database.
Meanwhile, a stub should be used when you want to prevent a specific method from being called directly.
var add = sinon.stub(firestore.db.collection, 'add')
With the comments below, it looks like you are trying to stub a complex object, in this case, you can actually assign a new value to the property without any sinon methods like this:
const fakeAdd = sinon.fake()
firestore.db.collection = [
{add: fakeAdd}
]
firestore.addEntry();
sinon.assert.calledOnce(fakeAdd)
And for async method unit testing, you can simply mark the test method as async as well.
it('should do something', async () => {
await firestore.addEntry()
})
A working codepen example:
https://codepen.io/atwayne/pen/VweOXpQ

Sinon spy calledWithNew not working

I'm trying to use sinon or sinon-chai's calledWithNew (or simply called), but can't seem to get it to work, I've looked at a few suggestions online without luck either, here is the function I'm trying to test:
users.js
exports.create = function (data) {
//some validation
var user = new User(data);
return user.save().then((result) => {
return mailer.sendWelcomeEmail(data.email, data.name).then(() => {
return {
message: 'User created',
userId: result.id
};
});
}).catch((err) => {
return Promise.reject(err);
});
}
Here is my test:
users.test.js
beforeEach(() => {
saveStub = sandbox.stub(User.prototype, 'save').resolves(sampleUser);
spy = sandbox.spy(User);
});
afterEach(() => {
sandbox.restore();
});
it('should call user.save', async () => {
result = await users.create(sampleArgs);
expect(saveStub).to.have.been.called; //-> true
expect(spy).to.have.been.called; //-> false, calledWithNew returns same result as well
});
I found several posts suggesting spying on (window, 'className') but I'm using mocha, not a browser.
Trying to spy on (global, User / User.prototype) didn't work either.
User is a module-level variable in users.js. Sinon cannot affect it. When you do this in your test file:
spy = sandbox.spy(User);
You're creating a spy in the scope of your test file, sure, but your module is still using the original.
To do something like this, you need to export your constructor inside an object, then both invoke it and spy on it through that object:
Wherever User is coming from, let's say user.js:
class User {
// whatever your User implementation is
}
module.exports = { User };
users.js:
const userModule = require('./user');
...
var user = new userModule.User(data);
Then, in your test file:
const userModule = require('./user');
spy = sandbox.spy(userModule, 'User');
Another way to do this would be to use something like proxyquire. It can make these kinds of tests less obtrusive, but can make your tests more confusing to readers.
My preference is generally to keep constructors very simple so I don't ever have to spy on them. I've never used calledWithNew in any of my own projects for this reason. :\ It's up to you, though.

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.

How to mock an external module's function with Jest

Can Jest's mocking handle functions from modules I didn't write?
node-yelp-api-v3 has Yelp.searchBusiness(String) but my attempts to use Jest's mocking functionality are unsuccessful. The Jest examples seem to assume that I'm mocking a module I have in the project. From the documentation I'm also unclear how to mock a specific function in a module.
Neither of these are working:
jest.mock('Yelp.searchBusiness', () => {
return jest.fn(() => [{<stubbed_json>}])
})
or
jest.mock('Yelp', () => {
return jest.fn(() => [{<stubbed_json>}])
})
I'm currently using sinon but would like to use just Jest. This Sinon approach works:
var chai = require('chai')
var should = chai.should()
var agent = require('supertest').agent(require('../../app'))
const Yelp = require('node-yelp-api-v3')
var sinon = require('sinon')
var sandbox
describe('router', function(){
beforeEach(function(){
sandbox = sinon.sandbox.create()
stub = sandbox.stub(Yelp.prototype, 'searchBusiness')
})
afterEach(function(){
sandbox.restore()
})
it ('should render index at /', (done) => {
/* this get invokes Yelp.searchBusiness */
agent
.get('/')
.end(function(err, res) {
res.status.should.equal(200)
res.text.should.contain('open_gyro_outline_500.jpeg')
done()
})
})
})
Mocking external modules is explained here.
If the module you are mocking is a Node module (e.g.: lodash), the mock should be placed in the __mocks__ directory adjacent to node_modules (unless you configured roots to point to a folder other than the project root) and will be automatically mocked. There's no need to explicitly call jest.mock('module_name').
For your exact case this would mean you need to create a folder __mocks__ with a file node-yelp-api-v3.js in it. In that file you create a mock object from the original module using genMockFromModule and override the method you want to mock.
// __mocks__/node-yelp-api-v3.js
const yelp = jest.genMockFromModule('node-yelp-api-v3')
function searchBusiness() {
return [{<stubbed_json>}]
}
yelp.searchBusiness = searchBusiness
module.exports = yelp
Additionally you could also wrap the searchBusiness in jest.fn if you want to call assertions like searchBusiness.mock.calls.length for this method later.
You can also do this:
jest.mock('Yelp', () => ({
searchBusiness: jest.fn(() => [{<stubbed_json>}])
})
And then you'll be able to call things like expect(Yelp.searchBusiness).toHaveBeenCalled() etc.

Resources