How to make Mocha unit test wait for async variable - node.js

I am very new to unit testing and I am trying to write a unit test on a function that reads from my database, but because the read is asynchronous the value is always undefined in my Mocha unit test. How can I do an assert on a variable which is declared asynchronously?
Unit Test:
const homeController = require('../controllers/homeController');
describe('Testing home_get function', function () {
it ('should render the home page', function () {
const req = {};
homeController.home_get(req, null, () => {});
console.log("numberUsers", numberUsers);
assert.ok(numberUsers > 0);
});
});
Function I am trying to test:
module.exports.home_get = async (req, res) => {
requestTime = Date.now();
numberUsers = await User.countDocuments({});
numberViewers = await Viewer.countDocuments({});
numberAccreditors = await Accreditor.countDocuments({});
res.status('200').render('home', {numberUsers: numberUsers, numberViewers: numberViewers, numberAccreditors: numberAccreditors});
};
When I do an assert statement on the requestTime variable it works as intended but when I do it on the async variables the value is always undefined and I am not sure how to get the unit test to work on these async variables, also.... how would you then do a unit test to check the res.status is correct when you render home?

the main goal in unit testing is to only test 1 layer of logic, such as the function itself you have presented. every other thing must be a mock, otherwise it is an integration test that you require and not a unit test.
in this case, you are trying to actually run a query on a database, but it is not actually up and working, so what you get is undefined.
i suggest the following:
if you want to actually query the database, run an integration test instead where it will actually work. integration tests are loading the whole application to test the interaction between all of the components.
to complete the unit test itself, you should mock the database such as it returns what you expect it to return, so only those 5 lines in the function is actually running.

You have two way to do that.
You can await your function in the test:
it ('should render the home page', async function () {
const req = {};
await homeController.home_get(req, null, () => {});
console.log("numberUsers", numberUsers);
assert.ok(numberUsers > 0);
});
or use the test callback function:
it ('should render the home page', function (done) {
const req = {};
homeController.home_get(req, null, () => {}).then(() => {
console.log("numberUsers", numberUsers);
assert.ok(numberUsers > 0);
done();
});
});
But in either case your function should return a value on which you can make the assert. There, the numberUsers is never declared and will still be undefined.
edit: Except if registered in the globals object because not using let, const or var. This is something you probably do not want to do.

Related

How to mock a function from the same module as the function being tested

So, I have two methods on a Node project:
export function methodA() {
const response = await methodB();
return response.length ? 'something' : 'else';
}
export function methodB() {
const array = await getData(); // Access database and make API calls
return array[];
}
methodA calls methodB and methodB makes stuff that I don't care right now for my unit testing purposes, so I want to mock methodB so it will return an empty array and won't try to make any database or API calls. The issue is that I can't actually mock methodB, as my test is still calling the actual function.
Here's my test:
describe('method testing', () => {
it('calls method', async () => {
const response = await myModule.methodA();
expect(response).toBe('else');
});
});
That test is failing, because jest is still calling the actual methodB which is meant to fail, as it can't connect to the database or reach APIs, so, this is what I tried doing to mock methodB:
Spying on the method:
import * as myModule from '#/domains/methods';
jest.spyOn(myModule, 'methodB').mockImplementation(() => [] as any);
// describe('method testing', () => {...
Mocking the entire file except for the methodA:
jest.mock('#/domains/methods', () => {
const originalModule = jest.requireActual('#/domains/methods')
return {
...originalModule,
methodB: jest.fn().mockReturnValue([])
}
});
// describe('method testing', () => {...
I have also tried:
Mocking methodB inside each test and inside describe
Spying methodB inside each test and inside describe
Some variations of those examples I wrote above
I'm not entirely sure on what to do right now, so any light would be appreciated.
#Update: Altough the problem is similar, this is not a duplicate question and this (How to mock functions in the same module using Jest?) does not answer my question.

How can a jest test waitFor a mocked function not to have been called?

Given an implementation that looks something like the following; in a unit test, how can one assert that a mock of someExternalCallback is never called?
const MyComp = ({ isLoading, children }) => {
useEffect(() => {
if (!isLoading) {
someExternalAsyncFunction().then(someExternalCallback);
}
}, [isLoading]);
return <>{children}</>;
}
Assuming that both external functions are mocked in the test file, and there's a test that looks like the following, how can the inverse be tested?
it("should call someExternalCallback when [...]", async () => {
// stuff
await waitFor(() => expect(someExternalCallbackMock).toHaveBeenCalledTimes(1));
});
For the inverse, I've currently written a test that looks like the following.
it("should never call someExternalCallback when [...]", async () => {
// different stuff
await waitFor(() => expect(someExternalCallbackMock).not.toHaveBeenCalled());
});
But I'm assuming the combination of waitFor and .not.toHaveBeenCalled doesn't actually test the intended situation. My understanding is that waitFor calls the callback it's given many times until it passes, but in this situation the test wants to assert that something never happens.

Sinon stub not allowing for test coverage on actual function

Here is the function under test:
export const callStoredProcedure = async (procedureName, procedureArgs)
=> {
let resultSet: any;
try {
await knexClient.transaction(async trx => {
await trx.raw(`CALL ${procedureName}(${procedureArgs});`).then(result => {
if (result[0] && result[0][0]) {
resultSet = JSON.stringify(result[0][0]);
}
});
});
} catch (err) {
console.log(err);
}
return resultSet;
};
And here I'm mocking the knexClient in a beforeEach block:
let knexClientMock;
beforeEach(() => {
// Stub Data source clients
const rawStub = sinon.stub().resolves([{ affectiveRows: 0, insertId: 0 }]);
knexClientMock = sinon.stub(knexClient, 'transaction').callsFake(
async (): Promise<any> => {
return {
raw: rawStub,
};
},
);
});
However, when I run my tests the if statement if (result[0] && result[0][0]) never runs and Jest coverage shows everything is uncovered from the raw statement down to the catch.
The mocking seems to be working correctly in that it uses the mock knexClient.transaction, but it leaves the rest of the lines uncovered. I think I just want to mock the raw function with a mock promise result and allow the .then function to run.
How can I mock the knexClient.transaction.raw function only and allow the rest of the code to have test coverage?
The issue is that your test stubs do not correctly mock the objects being used:
The "transaction" method in your code takes a single argument (a callback function) and invokes this callback, passing it a transaction executor that has an asynchronous "raw" method.
Your test stubs make the "transaction" method return an object that has an async raw method.
When writing unit tests for your code, it is very important to understand how your code works, first. Otherwise it is extremely easy to write meaningless tests that won't protect against introducing bugs.

Can you make Supertest wait for an Express handler to finish executing?

I use Supertest to test my Express apps, but I'm running into a challenge when I want my handlers to do asynchronous processing after a request is sent. Take this code, for example:
const request = require('supertest');
const express = require('express');
const app = express();
app.get('/user', async (req, res) => {
res.status(200).json({ success: true });
await someAsyncTaskThatHappensAfterTheResponse();
});
describe('A Simple Test', () => {
it('should get a valid response', () => {
return request(app)
.get('/user')
.expect(200)
.then(response => {
// Test stuff here.
});
});
});
If the someAsyncTaskThatHappensAfterTheResponse() call throws an error, then the test here is subject to a race condition where it may or may not failed based on that error. Even aside from error handling, it's also difficult to check for side effects if they happen after the response is set. Imagine that you wanted to trigger database updates after sending a response. You wouldn't be able to tell from your test when you should expect that the updates have completely. Is there any way to use Supertest to wait until the handler function has finished executing?
This can not be done easily because supertest acts like a client and you do not have access to the actual req/res objects in express (see https://stackoverflow.com/a/26811414/387094).
As a complete hacky workaround, here is what worked for me.
Create a file which house a callback/promise. For instance, my file test-hack.js looks like so:
let callback = null
export const callbackPromise = () => new Promise((resolve) => {
callback = resolve
})
export default function callWhenComplete () {
if (callback) callback('hack complete')
}
When all processing is complete, call the callback callWhenComplete function. For instance, my middleware looks like so.
import callWhenComplete from './test-hack'
export default function middlewareIpnMyo () {
return async function route (req, res, next) {
res.status(200)
res.send()
// async logic logic
callWhenComplete()
}
}
And finally in your test, await for the callbackPromise like so:
import { callbackPromise } from 'test-hack'
describe('POST /someHack', () => {
it.only('should handle a post request', async () => {
const response = await request
.post('/someHack')
.send({soMuch: 'hackery'})
.expect(200)
const result = await callbackPromise()
// anything below this is executed after callWhenComplete() is
// executed from the route
})
})
Inspired by #travis-stevens, here is a slightly different solution that uses setInterval so you can be sure the promise is set up before you make your supertest call. This also allows tracking requests by id in case you want to use the library for many tests without collisions.
const backgroundResult = {};
export function backgroundListener(id, ms = 1000) {
backgroundResult[id] = false;
return new Promise(resolve => {
// set up interval
const interval = setInterval(isComplete, ms);
// completion logic
function isComplete() {
if (false !== backgroundResult[id]) {
resolve(backgroundResult[id]);
delete backgroundResult[id];
clearInterval(interval);
}
}
});
}
export function backgroundComplete(id, result = true) {
if (id in backgroundResult) {
backgroundResult[id] = result;
}
}
Make a call to get the listener promise BEFORE your supertest.request() call (in this case, using agent).
it('should respond with a 200 but background error for failed async', async function() {
const agent = supertest.agent(app);
const trackingId = 'jds934894d34kdkd';
const bgListener = background.backgroundListener(trackingId);
// post something but include tracking id
await agent
.post('/v1/user')
.field('testTrackingId', trackingId)
.field('name', 'Bob Smith')
.expect(200);
// execute the promise which waits for the completion function to run
const backgroundError = await bgListener;
// should have received an error
assert.equal(backgroundError instanceof Error, true);
});
Your controller should expect the tracking id and pass it to the complete function at the end of controller backgrounded processing. Passing an error as the second value is one way to check the result later, but you can just pass false or whatever you like.
// if background task(s) were successful, promise in test will return true
backgroundComplete(testTrackingId);
// if not successful, promise in test will return this error object
backgroundComplete(testTrackingId, new Error('Failed'));
If anyone has any comments or improvements, that would be appreciated :)

Mocha, Sinon and Chai testing two http calls inside a callback

I am doing some really simple testing with Chai Mocha and Sinon. I am wondering how you would go about testing a http method that gets called inside of a callback. Please point me in the right direction if you can, struggling to find anything on it, I know you can do it, just not sure how. My code is below:
index.js
const http = require('http')
class Index {
add(a, b) {
return a + b
}
get(uri) {
http.get(uri, () => {
http.get('/', function() {
return
})
})
}
}
module.exports = Index
index.spec.js
const Index = require('../index')
const http = require('http')
const { stub, fake, assert } = require('sinon')
const { expect } = require('chai')
let httpSpy;
beforeEach(function () {
a = new Index()
httpSpy = stub(http, 'get')
})
describe('When the get method is invoked', function () {
beforeEach(function () {
a.get('http://www.google.co.uk')
})
it('should make a call to the http service with passed in uri', function () {
assert.calledOnce(httpSpy) // This really should be called twice (Part I am struggling with)
assert.calledWith(httpSpy, 'http://www.google.co.uk')
// I want to test instead that the httpSpy was called twice as, inside the get method, when the first http get resolves, another one gets fired off also
})
})
There are two issues.
Firstly, we cannot tell when the Index.get() method execution is ended (it does not accept a callback, neither return a promise, not marked as async etc).
get(uri) { ... }
Usage of such method is critically inconvenient. For example: if we'd want to first do Index.get() and then do some action right after we won't be able to.
To fix the this we can just add a callback as a last parameter of Index.get.
The second issue is how the http.get method is stubbed:
httpSpy = stub(http, 'get')
This line basically means: replace http.get with an empty function. That dummy function won't throw if you pass a callback inside but it won't call it.
That is why http.get is called only once. It simply ignores the passed callback where http.get should be called the second time.
To fix this we can use stub.yields() method to make sinon aware that the last parameter passed to a stub is a callback (and sinon needs to call it). You can find the method in the docs.
Here is a working example, please see my comments:
class Index {
// Added a callback here
get(uri, callback) {
http.get(uri, () => {
http.get('/', () => {
// Pass any data you want to return here
callback(null, {});
})
})
}
}
let httpSpy;
beforeEach(() => {
a = new Index()
// Now sinon will expect a callback as a last parameter and will call it
httpSpy = stub(http, 'get').yields();
})
describe('When the get method is invoked', () => {
const uri = 'http://www.google.co.uk';
// Now we are waiting for the execution to end before any assertions
beforeEach(done => {
a.get(uri, done);
});
it('should make a call to the http service with passed in uri', () => {
assert.calledTwice(httpSpy);
assert.match(httpSpy.getCall(0).args[0], uri);
assert.match(httpSpy.getCall(1).args[0], '/');
});
})

Resources