Using Sinon to stub chained Mongoose calls - node.js

I get how to stub Mongoose models (thanks to Stubbing a Mongoose model with Sinon), but I don't quite understand how to stub calls like:
myModel.findOne({"id": someId})
.where("someBooleanProperty").equals(true)
...
.exec(someCallback);
I tried the following:
var findOneStub = sinon.stub(mongoose.Model, "findOne");
sinon.stub(findOneStub, "exec").yields(someFakeParameter);
to no avail, any suggestions?

I've solved it by doing the following:
var mockFindOne = {
where: function () {
return this;
},
equals: function () {
return this;
},
exec: function (callback) {
callback(null, "some fake expected return value");
}
};
sinon.stub(mongoose.Model, "findOne").returns(mockFindOne);

Take a look to sinon-mongoose. You can expects chained methods with just a few lines:
sinon.mock(YourModel).expects('findOne')
.chain('where').withArgs('someBooleanProperty')
.chain('exec')
.yields(someError, someResult);
You can find working examples on the repo.
Also, a recommendation: use mock method instead of stub, that will check the method really exists.

Another way is to stub or spy the prototype functions of the created Query (using sinon):
const mongoose = require('mongoose');
sinon.spy(mongoose.Query.prototype, 'where');
sinon.spy(mongoose.Query.prototype, 'equals');
const query_result = [];
sinon.stub(mongoose.Query.prototype, 'exec').yieldsAsync(null, query_result);

If you use Promise, you can try sinon-as-promised:
sinon.stub(Mongoose.Model, 'findOne').returns({
exec: sinon.stub().rejects(new Error('pants'))
//exec: sinon.stub(). resolves(yourExepctedValue)
});

I use promises with Mongoose and stub the methods like this:
const stub = sinon.stub(YourModel, 'findById').returns({
populate: sinon.stub().resolves(document)
})
Then I can call it like:
const document = await YourModel.findById.populate('whatever');

Using Multiple mongoose method stub using below code.
Service code
models.Match.findOne({ where: { id: ChangeInningInputType.id } })
.then((match) => {
if (!match) {
throw Error('Match not found');
}
models.Match.update(
{ inning: ChangeInningInputType.inning },
{ where: { id: ChangeInningInputType.id } },
);
return resolve(match);
})
.catch((error) => {
return reject(error);
});
Testcase code
it('Success test case for inning update', async () => {
const bulkCreateStub = sinon
.stub(models.Match, 'findOne')
.resolves(inningResponse);
const updateStub = sinon
.stub(models.Match, 'update')
.resolves(inningResponse);
const aa = await changeInning(
{},
{
ChangeInningInputType: inningRequest,
},
);
console.log('updateStub===>', updateStub);
expect(updateStub.calledOnce).to.equal(true);
expect(bulkCreateStub.calledOnce).to.equal(true);
});

Related

Function that return undefined in node.js. Inside the function I have mongoDB.connect

I tried to make function for my project in the service. This service need to check is user exists in my database, but my function(this function is checking) inside the class return undefined.
This is my code:
const express = require("express");
const mongoDB = require('mongodb').MongoClient;
const url = "here I paste url to my databse(everything is good here)";
class CheckService {
isUserExists(username) {
mongoDB.connect(url, (error, connection) => {
if (error) {
console.log("Error", '\n', error);
throw error;
}
const query = {name: username};
const db = connection.db("users");
const result = db.collection("users").find(query).toArray(
function findUser(error, result) {
if (error) {
throw error;
}
const arr = [];
if (result.value === arr.value) {
console.log(false);
connection.close();
return false;
} else {
console.log(true);
console.log(arr);
console.log(result);
connection.close();
return true;
}
});
console.log(result);
});
}
}
module.exports = new CheckService();
I imported my service to another service:
const checkService = require('./check.service');
After this, I invoked my function from my service like this:
console.log('function:',checkService.isUserExists(username));
I expected good result, but function doesn't return, that I want, unfortunately.
Please help!
There are two problems with your function
it doesn't return anything
toArray() is a promise, so your console.log probably just prints a promise.
Probably you're looking for something like this:
class CheckService {
async isUserExists(username) {
const connection = await mongoDB.connect(url);
const query = {name: username};
const db = connection.db("users");
const result = await db.collection("users").find(query).toArray();
connection.close();
return result.length !== 0;
}
}
You'd then invoke it with something like
await new CheckService().isUserExists(username);
Though, it's worth noting that you probably don't need toArray and instead could use findOne or even count() since all you care about is existence. I probably also wouldn't instantiate a new connection each time (but that's not super relevant)
2 things wrong here. Firstly the first comment is correct. You're only logging the result and not passing back to the caller. Secondly, a quick peek at the mongo docs shows that retrieval methods are promises. You need to make the function async and add awaits where needed.
Mongo:
https://www.mongodb.com/docs/drivers/node/current/fundamentals/crud/read-operations/retrieve/
Promises:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

Aync / Await return pending

Why is my Aync Await Function returning an Pending Promise ? i already tried .then statement but it doesn't work, here is my code :
const findData = async () => {
let query = await userSchema.findOne({ _id: research["uploaderID"] });
return query;
};
research["uploaderInfo"] = findData();
console.log(findData());
when i tried to console.log the findData, it just gave me this :
Promise { <pending> }
Promise { <pending> }
and when i tried to check the research object, it was empty, but when i tried to add a console.log(query) inside the findData() function, it gave me the expected result, which mean the query is correct, and this is an issue because of the async / await.
UPDATE
i tried #dai solution to add await when i tried to set my research like this
const findData = async () => {
let query = await userSchema.findOne({ _id: research["uploaderID"] });
return query;
};
async () => {
research["uploaderInfo"] = await findData();
};
when i tried this, any code that i put inside the second nameless async function does not work, i tried to set the object to random string and it still doesn't changing
Read this: https://stackoverflow.com/a/56590390/159145
Short answer: Run NodeJS 14.17 (or any version after 13.3+) with the flag --harmony-top-level-await and you can have this:
// program.js
const findData = async () => {
let query = await userSchema.findOne({ _id: research["uploaderID"] });
return query;
};
research["uploaderInfo"] = await findData();
console.log(research["uploaderInfo"]);
...or even just this (assuming your research and userSchema objects are trivially instantiated):
// program.js
research["uploaderInfo"] = await userSchema.findOne({ _id: research["uploaderID"] });
console.log(research["uploaderInfo"]);
Longer answer without NodeJS flags:
It looks like NodeJS 14.17 doesn't yet support "top-level await" (someone correct me if I'm wrong) but that's not a real problem: it just means you need to wrap all of your original top-level ("root function") code in an async function and invoke it immediately.
Like so (I've named the function entrypoint, though you can use an anonymous function if you like):
// program.js
async function entrypoint() {
const research = ...
const userSchema = ...
const findData = async () => {
let query = await userSchema.findOne({ _id: research["uploaderID"] });
return query;
};
research["uploaderInfo"] = await findData();
console.log(research["uploaderInfo"]);
}
entrypoint();
Note that you can elide and inline findData's function and call userSchema.findOne directly:
// program.js
async function entrypoint() {
const research = ...
const userSchema = ...
research["uploaderInfo"] = await await userSchema.findOne({ _id: research["uploaderID"] });
console.log(research["uploaderInfo"]);
}
entrypoint();

Mocking with Sinon against a service module

I have got myself to the stage of unit testing, and to be honest, with all the different examples online I have got myself confused. I have a good understanding of Mocha & Chai, but Sinon is a different story.
So I have what I think is a pretty straight forward setup. I have a POST route that calls a controller. This controller is like so (removed some basic validation code)
const { createUser } = require('../services/user.service');
const apiResponse = require('../helpers/apiResponse');
const postUser = async (req, res) => {
const user = {
account_id: req.body.id,
status: req.body.status,
created_at: new Date(),
updated_at: new Date(),
};
const result = await createUser(user);
return apiResponse.successResponseWithData(res, 'User added.', result.affectedRows);
} catch (err) {
return apiResponse.errorResponse(res, err);
}
};
module.exports = {
postUser,
};
So all it really does is validate, and then creates a user object with the req and pass that to a service class. This services class does nothing more than pass the data to a database class.
const { addUserToDb } = require('../database/user.db');
const createUser = async (user) => {
try {
const createdUser = await addUserToDb(user);
return createdUser;
} catch (err) {
throw new Error(err);
}
};
module.exports = {
createUser,
};
I wont show the database class because what I want to focus on first is the controller, and then I can hopefully do the rest myself.
So from what I understand, I should be testing functions. If a function makes an external call, I should spy, mock, stub that call? I should only spy, mock or stub this functions dependencies, if one of the dependencies
has its own dependency (like the service module above having a database call dependency), this should be performed in another test? Sorry, just a few questions to help me understand.
Anyways, so I have created a user.controller.test.js file. I have not got far with it, but this is what I have so far
const chai = require('chai');
const sinon = require('sinon');
const { expect } = chai;
const faker = require('faker');
const controller = require('../controllers/user.controller');
const service = require('../services/user.service');
const flushPromises = () => new Promise(setImmediate);
describe('user.controller', () => {
describe('postUser', () => {
beforeEach(() => {
//I see a lot of code use a beforeEach, what should I be doing here?
});
it('should create a user when account_id and status params are provided', async () => {
const req = {
body: { account_id: faker.datatype.uuid(), status: 'true' },
};
const stubValue = {
id: faker.datatype.id(),
account_id: faker.datatype.uuid(),
status: 'true',
created_at: faker.date.past(),
updated_at: faker.date.past(),
};
});
});
});
If I am being totally honest I am pretty lost as to what I should be testing here. From my understanding, I need to mock the service module I think.
Could someone kindly provide some insight as to what I should be doing in this test?
Many thanks
Update
Thank you for your detailed response, I have managed to get a spy working which is a step forward. So I want to do a test on my service module, createUser method.
You can see that my createUser method takes a user Object as a parameter and passes this to a database module where it is inserted into the database and then the user object returned.
So when testing my service class, I need to mock this call to my database module.
const chai = require('chai');
const sinon = require('sinon');
const { expect } = chai;
const faker = require('faker');
const service = require('../services/user.service');
const database = require('../database/user.db');
describe('user.service', () => {
describe('createUser', () => {
it('should create a user when user object is provided', async () => {
const user = {
id: faker.datatype.string(),
status: 'true',
created_at: faker.date.past(),
updated_at: faker.date.past(),
};
const expectedUser = {
id: user.id,
status: user.status,
created_at: user.created_at,
updated_at: user.updated_at,
};
const mockedDatabase = sinon.mock(database);
mockedDatabase.expects('addUserToDb').once().withArgs(expectedUser);
await service.createUser(user);
mockedDatabase.verify();
mockedDatabase.restore();
});
});
});
When I test this, I seem to be getting this response, and it still seems to be inserting the record into my database.
ExpectationError: Expected addUserToDb({
id: 'yX7AX\\J&gf',
status: 'true',
created_at: 2020-06-03T03:10:23.472Z,
updated_at: 2020-05-24T14:44:14.749Z
}, '[...]') once (never called)
at Object.fail (node_modules\sinon\lib\sinon\mock-expectation.js:314:25)
Do you have any idea what I am doing wrong?
Thanks
before I try, I would like to suggest to drop the try/catch blocks everywhere, I will assume you're using expressJs in your Node application, and for such, take a look at express-promise-router as using that Router (instead the default one) will automatically catch anything it was thrown and you just need to focus on the code...
taking your example, you would write:
const { addUserToDb } = require('../database/user.db');
const createUser = async (user) => addUserToDb(user);
module.exports = {
createUser,
};
and
const { createUser } = require('../services/user.service');
const apiResponse = require('../helpers/apiResponse');
const postUser = async (req, res) => {
const { id: account_id, status } = res.body;
const result = await createUser({ account_id, status }); // set the date in the fn
return apiResponse.successResponseWithData(res, 'User added.', result.affectedRows);
};
module.exports = {
postUser,
};
if there's an error and in some place on the route an error is thrown, you will get a nice message back in the response with the error
regarding the code it self, seems a lot cleaner to read - keep in mind that code is for humans, the machine does not even care how you name your variables 😊
Now, regarding the tests ... I do tend to split things into 3 parts
unit tests: the functions itself, single one, like validation, helpers, etc
integration tests: when you call your API endpoint what should be returned
GUI tests (or end-to-end/e2e): applied when a GUI exists, will skip this for now
so in your case, the first thing to make sure of is what are you testing... and taking that, start from the small blocks (unit tests) and move up to the blocks that make sure all is glued together (e2e)
So all it really does is validate, and then creates a user object with the req and pass that to a service class. This services class does nothing more than pass the data to a database class.
Seems a great way to start, so it "validates" ... let's test our validation, let's pass null, undefined, string when all you want is int and so on, until we get a pretty good idea that whatever it passes, we will reply correctly with and without an error
Note I tend to use OpenAPI specs, which makes things easier for me as it provides 2 things
documentation of the endpoints
validation of the endpoints with a nice error message to the consumer
and yes, I always test some validation just to make sure it's working as expected, even though I trust the tool 100% 😜
So from what I understand, I should be testing functions.
well, an application is a group of functions, so all good there 💪
If a function makes an external call, I should spy, mock, stub that call?
I'll try to explain as best as I can what spies, stubs and mocks in Sinon are, please be gentle 🙏
Spies
they tell us information about functions calls, like, number of times called, arguments, return value, and more - they have two types, anonymous spies or spies that wrap methods in our code
function testMyCallback(callback) { callback(); }
describe('testMyCallback fn', function() {
it('should call the callback', function() {
const callbackSpy = sinon.spy(); // anonymous spy - no arguments
testMyCallback(callbackSpy);
expect(callbackSpy).to.have.been.calledOnce;
});
});
const user = {
setNname: function(name) {
this.name = name;
}
}
describe('setname fn', function() {
it('should be called with name', function() {
const setNameSpy = sinon.spy(user, 'setName'); // wrap method spy
user.setName('Katie');
expect(setNameSpy).to.have.been.calledOnce;
expect(setNameSpy).to.have.been.valledWith('Katie');
setNameSpy.restore(); // to remove the Spy and prevent future errors
});
});
Stubs
are power-spies, as they have all the functionality of Spies, but they replace the target function, they have methods that can return a specific value or throw a specific exception and a bit more
they are great to be used with your question regarding external calls, as they replace calls (so you can mock the call behavior and never use the original call)
the simplest of the examples is:
function isAdult(age) {
return age > 21;
}
describe('Sinon Stub Example', () => {
it('should pass', (done) => {
const isAdult = sinon.stub().returns('something');
isAdult(0).should.eql('something');
isAdult(0).should.not.eql(false);
done();
});
});
we've STUB'ed our function, and explicitly said it's a "function" that returns a string something... and for now on, we will never need to go to the function itself, as we have STUB it, we've replaced the real behavior with our own
another example of using STUBs when calling our API application in our integration tests
describe('when we stub our API call', () => {
beforeEach(() => {
this.get = sinon.stub(request, 'get'); // stub "request.get" function
});
afterEach(() => {
request.get.restore(); // remove our power-spy
});
describe('GET /api/v1/accounts', () => {
const responseObject = {
status: 200,
headers: {
'content-type': 'application/json'
}
};
const responseBody = {
status: 'success',
data: [
{
accountId: 1,
status: 'active'
},
{
accountId: 2,
status: 'disabled'
}
]
};
it('should return all accounts', (done) => {
// the 3 objects of our callback (err, res, body)
this.get.yields(null, responseObject, JSON.stringify(responseBody));
request.get(`${base}/api/v1/movies`, (err, res, body) => {
expect(res.statusCode).to.be.eql(200);
expect(res.headers['content-type']).to.contain('application/json');
body = JSON.parse(body);
expect(body).to.be.an('array').that.includes(2);
done();
});
});
});
});
you can also stub axios, but you will need a new library, either moxios, or proxyquire or more...
Mocks
are a bit similar to Stubs (our Power-Spies) but they can be used to replace whole objects and alter their behavior, they are mostly used when you need to stub more than one function from a single object - if all you need is to replace a single function, a stub is easier to use
Mocks can make things oversimplify and you could break your application without even knowing, so be aware...
a normally use is, for example
function setupNewAccount(info, callback) {
const account = {
account_id: info.id,
status: info.status,
created_at: new Date(),
updated_at: new Date()
};
try { Database.save(account, callback); }
catch (err) { callback(err); }
}
describe('setupNewAccount', function() {
it('', function() {
const account = { account_id: 1, status: 'active' };
const expectedAccount = {
account_id: account.id, status: account.status
};
const database = sinon.mock(Database);
database.expectes('save').once().withArgs(expectedAccount);
setupNewAccount(account, function() {});
database.verify();
database.restore();
});
});
something that we will keep forgetting is the .restore() part, and for that, there's a package (one more...) called sinon-test that will auto cleanup at the end of a test
I just hope it helped you with some of your questions and it's a bit clearer now 😏
BTW, for stubbing HTTP requests, I use nock as I think it's much easier to read and use than Sinon, especially for anyone that is reading code for the first time and has no experience in either Sinon or Nock...

Use bluebird for mongoose, got ".bind is not a function"

I use bluebird for mongoose:
const Promise = require("bluebird");
const mongoose = require("mongoose");
mongoose.Promise = Promise;
And I want to use Promise.bind to share variable in the promise chain:
function getAutherOfBook(name)
{
return Book.findOne(
{
name: name
}, "-_id auther")
.then(doc =>
{
return doc.auther;
});
};
function geNationalityOfAuther(name)
{
return Auther.findOne(
{
name: name
}, "-_id nationality")
.then(doc =>
{
return doc.nationality;
});
};
getAutherOfBook("The Kite Runner")
.bind({})
.then(auther =>
{
this.auther = auther;
return geNationalityOfAuther(auther);
})
.then(nationality =>
{
console.log("auther: ", this.auther);
console.log("nationality: ", nationality);
})
.bind()
But I got the error: getAutherOfBook(...).bind is not a function
Maybe bluebird is not working for mongoose?
The problem you are having is that mongoose queries does not return full fledge promises -- directly quoting http://mongoosejs.com/docs/promises.html (v4.7.6)
// A query is not a fully-fledged promise, but it does have a `.then()`.
query.then(function (doc) {
// use doc
});
// `.exec()` gives you a fully-fledged promise
var promise = query.exec();
assert.ok(promise instanceof require('mpromise'));
In other words, the then function is syntax sugar and not a promise which is why the bind and other promise functions does not work.
To make it work, you either wrap it up in a full promise or use the exec function as suggested in the docs

Unit testing / Mocking mongoose models with sinon

so I have a node module like:
let mongoose = require('mongoose'),
User = mongoose.model('User');
module.exports = (req, res, next) => {
User.findById(req.user)
.then(user => {
req.body.user = user._id;
req.body.company = user.company;
next();
}, err => {
req.send(400, err);
});
};
So, in this case, I want to ensure the proper things are attached to the req.body. So, how would I go about mocking the User function? I have to load the model first so this code doesn't throw an error before calling mongoose.model so maybe something to do with actually stubbing the global require? Thanks for any advice!
So, I just discovered proxyquire https://www.npmjs.com/package/proxyquire and it was like the heavens opened and shined a light down upon me for the most glorious "ah ha" moment. No more having to load models in mongoose before trying to use them!
The code to mock mongoose looks something like:
const proxyquire = require('proxyquire'),
expect = require('chai').expect,
Promise = require('bluebird'),
_ = require('lodash'),
USERID = 'abc123',
COMPANY = 'Bitwise',
mongooseStub = {
mongoose: {
model: (name, schema) => ({
findById: () => {
return new Promise((resolve, reject) => {
resolve({_id: USERID, company: COMPANY});
});
}
})
}
},
attachUser = proxyquire('../lib/users/attach-user', mongooseStub);
That will effectively load the attach-user module, while stubbing out the mongoose.model function to return something I can control. Then the rest of my test just calls out to the module function and can make assertions as expected.

Resources