Completely replace value of variable in Node.js Unit test - javascript

I am trying to test a (to me) complex piece of code that I would like to just return dummy data so I can do assertions on how it is handled. Here is the code:
const { sql, dbConnPoolPromise } = require('../database/db.js');
router.get('/', async (req, res) => {
try {
const pool = await dbConnPoolPromise
const result = await pool.request()
.query(SQL_SELECT_ALL);
res.status(200);
res.json(result.recordset[0]);
} catch (err) {
res.status(500);
res.send("ERROR, General Get" + err.message);
}
});
I want to replace the const result with a set piece of dummy data, thus mocking the response a SQL server might give.
I cannot figure out how to do this.
I am currently using Jest as my testing suite and was hoping to use spyOn or Jest.fn() to replace it but that only works on functions. Is there a way in jest to replace variables? I cannot replace the entire router.get as I am testing that res.status(200) (amongst others) is being sent correctly to the client, I only want to replace the value of the variable.
If required the contents of dbConnPoolPromise are:
let sql = require('mssql');
const config = require('config');
let dbConnPoolPromise = new sql.ConnectionPool(config.get('connection'))
.connect()
.then(pool => {
return pool
})
.catch(err => console.log('Database Connection Failed - error: ', err))
module.exports = {
sql, dbConnPoolPromise, buildSelect
};

You can access your query from a service wrapper, something like.
export function async getAllItems() {
const pool = await dbConnPoolPromise
return await pool.request().query(SQL_SELECT_ALL)
}
Then you can use jest's manual mocks functionality https://jestjs.io/docs/en/manual-mocks to mock out the response of getAllItems.
As a broader comment, this is usually why we have patterns to access DB from a repository or service rather than directly hitting it from a controller or route like this. Once you wrap it in a service, it makes it so much easier for you to mock it out during a test.

Related

Node.js best way to handle promises

So I have code that looks like this in my controller.
const Users = require("../models/users");
class UserController() {
getUserById(req, res) {
const id = req.params.id;
const users = new Users();
const userData = users.getUserById(uid).then(function(result) {
//Do other logic here that depends on db result
return res.status(200).json({status:"success", user: result});
});
}
}
My question is, what's the difference between that and the following, and which is the better practice?
const Users = require("../models/users");
class UserController() {
async getUserById(req, res) {
const id = req.params.id;
const users = new Users();
const userData = await users.getUserById(uid);
return res.status(200).json({status:"success",user:userData});
}
}
The second function call is using async-await feature of ES8, This makes our code appear as if it is synchronous and clean also, without doing .then() and callback within it. Both fetch() and async-await work on promise though.
Aync-Await is much more readable if I can put it this way. No call backs used here. It simply waits for the first API call to happen and only when the data arrives (or Promise is resolved) we fire another API call. For programmer itself life becomes little easy with Async-Await syntax.
For Error handling you will need try{} catch{} block in Aync-await but in case of fetch(), .catch() block handles API Errors for us.

How to mock an axios request using sinon modules

There seems to be so many different ways to do this, but I am trying to use just sinon, sinon-test, chai/mocha, axios, httpmock modules. I am not able to successfully mock a GET call made using axios. I want to be able to mock the response from that axios call so the unit test won't actually have to make the external API request.
I've tried setting up a basic unit test by creating a sandbox, and using sinon stub to set up a GET call and specify the expected response. I'm unfamiliar with JavaScript and NodeJS.
// Main class (filename: info.js)
function GetInfo(req, res) {
axios.get(<url>).then(z => res.send(z.data));
}
// Test class (filename: info.test.js)
it ("should return info", () => {
const expectedResponse = "hello!";
const res = sinon.spy();
const aStub = sinon.stub(axios, "get").resolves(Promise.resolve(expectedResponse));
const req = httpMock.createRequest({method:"get", url:"/GetInfo"});
info.GetInfo(req, res);
// At this point, I need to evaluate the response received (which should be expectedResponse)
assert(res.data, expectedResponse); // data is undefined, res.status is also undefined
// How do I read the response received?
});
I need to know how to read the response that is supposed to be sent back (if it is being captured in the first place by sinon).
I'm assuming the response you're wanting to check is the z.data being passed to res.send(z.data)
I don't think your Sinon Spy is being set up correctly.
In your example, res is a function created by sinon. This function won't have a property data.
You probably want to create a spy like this:
const res = {
send: sinon.spy()
}
This gives you a res object which has a spy with the key send. You can then make assertions about the parameters used to call res.send
it ("should return info", () => {
const expectedResponse = "hello!";
const res = {
send: sinon.spy()
};
const aStub = sinon.stub(axios, "get").resolves(Promise.resolve(expectedResponse));
const req = httpMock.createRequest({method:"get", url:"/GetInfo"});
info.GetInfo(req, res);
// At this point, I need to evaluate the response received (which should be expectedResponse)
assert(res.send.calledWith(expectedResponse)); // data is undefined, res.status is also undefined
});
Dont know if this helps but you may not be getting the correct response because resolves is a return with a promise wrap over it.
So by using resolves and inside it a Promise.resolve you are actually returning Promise wrap in a Promise.
Maybe you can try changing the code to the one below.
const aStub = sinon.stub(axios, "get").resolves(Promise.resolve(expectedResponse));
to
const aStub = sinon.stub(axios, "get").resolves(expectedResponse);

Wait for routes and database to initialize before running Mocha tests

I have a NodeJS app that connects to a Mongo database and then initializes all the routes once the connection has been established. I want to add some Mocha tests using a server and supertest for test requests. However, to retrieve the server from the test.js file, I need to require() the main file and get the server variable from it. When I try to use it however, it complains that the variable is undefined, as the database hasn't loaded and the routes haven't been initialised.
main.js (extract)
// This is a custom mongo module that connects using my db string
// It stores the DB as a variable in another file, which is later retrieved by the route handlers using mongo.GetDB()
mongo.ConnectToDB(function(err, dbo){
if(err) throw err;
require("./api")(app);
require("./admin")(app);
require("./routes")(app);
});
// This obviously doesn't work, as it's outside of the callback
module.exports.server = app.listen(4500);
server.test.js (extract)
describe("loading express", (done) => {
let server;
beforeEach(() => {
// The server is returned, but none of the routes are included
server = require("../main").server;
});
afterEach(() => {
server.close();
});
// ...
});
Is there any way to wait for the database and the routes to initialize before the server variable is retrieved and the Mocha tests continue?
Have main.js export a Promise instead, something like:
module.exports.serverPromise = new Promise((resolve, reject) => {
mongo.ConnectToDB(function(err, dbo){
if(err) reject(err);
require("./api")(app);
require("./admin")(app);
require("./routes")(app);
resolve(app.listen(4500));
});
});
Then, after importing it, you can use it after calling .then on the Promise

Firebase Firestore: when do promises from offline write operations resolve?

I activated offline as stated in the docs like:
firebase
.firestore()
.enablePersistence()
.then(() => {
console.log('offlinemode acctivated')
})
The log appears as I would expect.
When adding data like so:
db
.collection('foo')
.add({foo: 'bar'})
.then(docRef => {
console.log('Added Foo: ', docRef.id)
// do some stuff here with the newly created foo and it's id.
})
.catch(console.error)
Neither .then() nor .catch() are getting called while offline. This is even though the object is added to the foo collection in my offline DB as this callback is executed:
db
.collection('foo')
.onSnapshot(callback)
Am I missing something? I would expect the promise to either fail or resolve, so I can react accordingly.
Promises from write operations in Firestore will only resolve when there is confirmation from the server that the write completed, even though they may successfully be written to local cache.
Here's my solution:
I wrap the call in a function that should eventually return a resolving promise no matter the offline/online status
I then get the saved doc from onSnapshot which returns the doc written to local cache (works both online and offline).
Here's my code (with a little typescript):
export function dbWritePromise(functionPromise: Promise<any>): Promise<any>{
if(window.navigator.onLine){
return functionPromise
}
else{
return Promise.resolve()
}
}
// I grabbed this function from a Github issue one upon a time
export function docSnapshotPromise(ref: firebase.firestore.DocumentReference): Promise<any>{
return new Promise((resolve, reject) => {
const unsubscribe = ref.onSnapshot(doc => {
resolve(doc)
unsubscribe()
}, err => {
reject(err)
unsubscribe()
})
})
}
In use (I'm using the update function here, but add would work the same way) This code is working with documents from a collection called organizations:
try{
//update org doc
await dbWritePromise(orgRef.update({
name: 'New and improved name here'
}))
// wait for this updated doc to be written to local cache, then we can get the updated org
const updatedOrgRef = await docSnapshotPromise(orgRef)
const updatedOrg = updatedOrgRef.data()
console.log(updatedOrg.name) // outputs the new and improved name
}
catch (err) { handleError(err) }
The error thrown might be some error with local cache, or it might be a server error such as a permissions error returned by the Firestore rules (when online). Obviously any error from the server during offline mode would silently fail, even when the app is back online.
I'd love to see other peoples' solutions here!

Async Mocha done() isn't called in my function

I call this function, console log is called but done() is not called:
import {Database} from "../../code/server/Database";
import 'mocha'
const expect = require('chai').expect
describe('Database save', function() {
it('should save without error', function(done) {
Database.saveSessionData(1, 2, 3, function(err, docs) {
expect(err).to.equal(null)
expect(docs.sessionOpenTime).to.equal(1)
expect(docs.sessionCloseTime).to.equal(2)
expect(docs.sessionLength).to.equal(3)
console.log(2222)
done()
})
})
})
Here is the result, 'Running tests' continues spinning on forever:
But if I change the 'Database' code into this, it works as expected:
setTimeout(function () {
console.log('lol')
done()
}, 1000)
What am I missing here?
Mocha test hangs since you have an opened database connection.
There are two options to solve this problem:
If you do not need a real database connection in your tests:
you can use sinon.stub() (https://www.npmjs.com/package/sinon) to return a predetermined response for async methods you use in your tests or sinon.spy() to make sure a stubbed method called exact number of times.
Here's a good article I just found to illustrate how to use sinon.js: https://semaphoreci.com/community/tutorials/best-practices-for-spies-stubs-and-mocks-in-sinon-js.
you can implement a dependency injection container to be able to replace your implementation of Database class to a Database class that does not perform I/O operations.
Although dependency injection implementations may vary depending on requirements some simple implementations are also available:
https://blog.risingstack.com/dependency-injection-in-node-js/
If you need to perform a real connection in your tests:
Consider adding an after hook to your mocha tests:
let's say mongodb is used as a database (it does not matter, but it would
be an actual working example)
const mongoose = require('mongoose');
const expect = require('chai').expect;
mongoose.connect('mongodb://localhost/test');
after(() => {
mongoose.connection.close();
});
describe('db connection', () => {
it('should make a test', () => {
expect(1).to.equal(1);
});
});

Categories

Resources