How to mock a function with callback parameters in JestJS - javascript

My NodeJS app has a function readFilesJSON() that calls fs.readFile(), which of course invokes a callback with the parameters (err,data). The Jest unit test needs to walk both the error path and the data path.
My solution was to mock the call to fs.readFile() (see below). The mock function simply passes error or data based on test logic. This approach works when there is only one function being tested. The trouble I am seeing occurs when there are multiple functions that call fs.readFile(). Jest runs all the tests concurrently and the asynchronous nature of the functions mean that there is no guaranteed ordering to the calls to fs.readFile(). This non-deterministic behavior wrecks both the error/data logic and the parameter-checking logic using toHaveBeenCalledWith().
Does Jest provide a mechanism for managing independent usage of the mocks?
function readFilesJSON(files,done) {
let index = 0;
readNextFile();
function readNextFile() {
if( index === files.length ) {
done();
}
else {
let filename = files[index++];
fs.readFile( filename, "utf8", (err,data) => {
if(err) {
console.err(`ERROR: unable to read JSON file ${filename}`);
setTimeout(readNextFile);
}
else {
// parse the JSON file here
// ...
setTimeout(readNextFile);
}
});
}
}
}
The injected function setup looks like this:
jest.spyOn(fs, 'readFile')
.mockImplementation(mockFsReadFile)
.mockName("mockFsReadFile");
function mockFsReadFile(filename,encoding,callback) {
// implement error/data logic here
}

You can separate the different scenarios in different describe blocks and call your function after you clear the previous calls on the observed function, not to get false positive results.
import { readFile } from "fs";
import fileParser from "./location/of/your/parser/file";
jest.mock("fs");
// mock the file parser as we want to test only readFilesJSON
jest.mock("./location/of/your/parser/file");
describe("readFilesJSON", () => {
describe("on successful file read attempt", () => {
let result;
beforeAll(() => {
// clear previous calls
fileParser.mockClear();
readFile.mockImplementation((_filename, _encoding, cb) => {
cb(null, mockData);
});
result = readFilesJSON(...args);
});
it("should parse the file contents", () => {
expect(fileParser).toHaveBeenCalledWith(mockData);
});
});
describe("on non-successful file read attempt", () => {
let result;
beforeAll(() => {
// clear previous calls
fileParser.mockClear();
readFile.mockImplementation((_filename, _encoding, cb) => {
cb(new Error("something bad happened"), "");
});
result = readFilesJSON(...args);
});
it("should parse the file contents", () => {
expect(fileParser).not.toHaveBeenCalled();
});
});
});

Related

Why isn't my jest mock function implementation being called?

I have the following jest test configuration for my collection of AWS JS Node Lambdas. I have a module called dynamoStore I reference in several different lambdas package.json and use within the lambdas. I am trying to get test one of these lambdas by mocking the dynamo store module as it makes calls to dynamoDb. The problem is that the jest.fn implementation never gets called. I confirmed this by sticking a breakpoint in that line as well as logging the value the calling methods returns from it.
When I check lambda1/index.js in the debugger getVehicleMetaKeysFromDeviceId() is a jest object but when it is called it doesn't use my mock implementation
How do I get this implementation to work? Have I set up my mock incorrectly?
dynamoStore/vehicleMetaConstraints
exports.getVehicleMetaKeysFromDeviceId= async (data) => {
return data
};
dynamoStore/index.js
exports.vehicleMetaConstraints = require("./vehicleMetaConstraints");
...
lambda1/index.js
const { vehicleMetaStore } = require("dynamo-store");
exports.handler = async (event, context, callback) => {
const message = event;
let vehicle_ids = await vehicleMetaStore.getVehicleMetaKeysFromDeviceId(message.id);
// vehicle_ids end up undefined when running the test
}
lambda1/index.test.js
const { vehicleMetaStore } = require("dynamo-store");
jest.mock("dynamo-store", () => {
return {
vehicleMetaStore: {
getVehicleMetaKeysFromDeviceId: jest.fn(),
},
};
});
describe("VehicleStorageLambda", () => {
beforeEach(() => {
jest.resetModules();
process.env = { ...env };
});
afterEach(() => {
jest.clearAllMocks();
});
test("Handles first time publish with existing device", async () => {
let functionHandler = require("./index");
vehicleMetaStore.getVehicleMetaKeysFromDeviceId.mockImplementationOnce(() =>
// This never gets called
Promise.resolve({
device_id: "333936303238510e00210022",
})
);
await functionHandler.handler({});
});
});
Remove the call to jest.resetModules() in beforeEach. That's re-importing your modules before each test, and wiping out your mocks.
https://stackoverflow.com/a/59792748/3084820

How to stub a "wrapper" function using Sinon?

I'm setting up a Lambda function (node.js) and for example's sake, we'll keep it minimal.
module.exports = (event, context, callback) {
console.log("hello world")
}
However, I've created a function to wrap the lambda function that allows me to perform some functions that are required before each Lambda executes (I have a collection of Lambda functions that are wired up using their Serverless Application Model (SAM)). It also allows me to consolidate some of the logging and error handling across each function.
// hook.js
const connect = fn => (event, context, callback) => {
someFunction()
.then(() => fn(event, context, callback))
.then(res => callback(null, res))
.catch(error => {
// logging
callback(error)
})
}
module.exports = { connect }
// index.js
const Hook = require("./hook")
exports.handler = Hook.connect((event, context, callback) => {
console.log("hello world")
})
The logic is working well and Lambda is processing it successfully. However, I'm trying to stub this Hook.connect function using SinonJS and in need of some guidance.
I simply want to stub it to return a resolved promise, that way we can proceed to handle the code within each Lambda function (fn(event, context, callback)).
const sinon = require("sinon")
const Hook = require("./hook")
const { handler } = require("./index")
const event = {} // for simplicity sake
const context = {} // for simplicity sake
const callback = {} // for simplicity sake
describe("Hello", () => {
let connectStub
beforeEach(() => {
connectStub = sinon.stub(Hook, "connect").callsFake()
afterEach(() => {
connectStub.restore()
})
it("works", () => {
const results = handler(event, context, callback)
// assert
})
})
I've tried a few different methods, from the basic, sinon.stub(Hook, "connect"), to the more complicated where I'm trying to stub private functions inside of the hook.js file using rewire.
Any help would be appreciated -- thank you in advance.
Here is a working test:
const sinon = require('sinon');
const Hook = require('./hook');
const event = {}; // for simplicity sake
const context = {}; // for simplicity sake
const callback = {}; // for simplicity sake
describe('Hello', () => {
let handler, connectStub;
before(() => {
connectStub = sinon.stub(Hook, 'connect');
connectStub.callsFake(fn => (...args) => fn(...args)); // create the mock...
delete require.cache[require.resolve('./index')]; // (in case it's already cached)
handler = require('./index').handler; // <= ...now require index.js
});
after(() => {
connectStub.restore(); // restore Hook.connect
delete require.cache[require.resolve('./index')]; // remove the modified index.js
});
it('works', () => {
const results = handler(event, context, callback); // it works!
// assert
});
});
Details
index.js calls Hook.connect to create its exported handler as soon as it runs, and it runs as soon as it is required...
...so the mock for Hook.connect needs to be in place before index.js is required:
Node.js caches modules, so this test also clears the Node.js cache before and after the test to ensure that index.js picks up the Hook.connect mock, and to ensure that the index.js with the mocked Hook.connect is removed from the cache in case the real index.js is needed later.

How can I unit test a function that uses promises and event emitters in Node.js?

My question is about unit testing with promises and event emitters in Node.js. I am using the jasmine framework if that matters.
The code below uses the https module of Node.js to send a request to an API. The API will return JSON. The JSON from the API is the "rawData" variable in the code below.
I want to unit test that the function returns JSON (and not a JavaScript object).
I have unsuccessfully tried several approaches to unit testing that aspect of this function:
1) I tried spying on the Promise constructor so that it would return a fake function which would simply return a JSON string.
2) I have tried spying on the .on('eventType', callback) function of EventEmitters in Node.js to fake a function that returns JSON.
My question is: are either of the two approaches above possible and/or recommend for accomplishing my goal? Is there a different approach to isolating the http request and emitting of events from my unit test objective? Do I need to rewrite this function to facilitate easier unit testing?
const https = require('https');
function getJSON() {
return new Promise((resolve, reject) => {
const request = https.get(someConfig);
request.on('response', resolve);
})
.then(msg => {
return new Promise((resolve, reject) => {
let rawData = '';
msg.on('data', chunk => { rawData += chunk });
msg.on('end', () => {
resolve(rawData);
});
});
})
.then(json => {
JSON.parse(json);
return json;
})
}
Is there a reason you want to stick to https for making a request? If not, your code and your testing can both become really simple. I'll give an example using axios.
Http request can look like this
getJSON() {
const url = 'https://httpbin.org/get';
return axios
.get(url)
.then(response => response);
}
and you can stub the get call with Sinon
lab.experiment('Fake http call', () => {
lab.before((done) => {
Sinon
.stub(axios, 'get')
.resolves({ data: { url: 'testUrl' } });
done();
});
lab.test('should return the fake data', (done) => {
const result = requestHelper.getJSON2();
result.then((response) => {
expect(response.data.url).to.eqls('testUrl');
axios.get.restore();
done();
});
});
});
With the existing code, nock would work like this
lab.experiment('Fake http call with nock', () => {
lab.test('should return the fake data', (done) => {
nock('https://httpbin.org')
.get('/get')
.reply(200, {
origin: '1.1.1.1',
url: 'http://testUrl',
});
const result = requestHelper.getJSON2();
result.then((response) => {
const result = JSON.parse(response);
console.log(JSON.parse(response).url);
expect(result.url).to.eqls('http://testUrl');
nock.cleanAll();
done();
});
});
});
Full code is here
I would say that you need to refactor the code a little bit to be more testable.
When I write unit tests for functions I keep below points in mind
You do not need to test for the inbuilt or library modules as they are already well tested.
Always refactor your functions to have very specific reponsibility.
Implementing these two in your example, i would separate the server call in a service module whose sole responsibility is to take url (and configurations, if any) make server calls.
Now, when you do that you get two benefits
1. you have a reusable piece of code which you can now use to make other server calls(also makes your code cleaner and shorter)
Since its a module you can now write seperate tests for that module and take the responsibility of checking whether server calls are made from your current module that uses it.
Now all thats left to test in your getJSON function is to spyOn that service module and use tohaveBeenCalledWith and check that data is properly parsed.You can mock the service to return your desired data.
1 its making a service call
so test for toHaveBeenCalledWith
2 its parsing to JSON
so test for valid/invalid JSON
also test for failures
//no need to test whether https is working properly
//its already tested
const https = require('https');
const service = require("./pathToservice");
function getJSON() {
return service.get(somConfig)
.then(json => {
JSON.parse(json);
return json;
})
}
//its cleaner now
//plus testable
I think you have not succeeded because you're returning directly like that. It should be like:
function getJSON(callback) {
(new Promise((resolve, reject) => {
const request = https.get(someConfig);
request.on('response', resolve);
}))
.then(msg => {
return new Promise((resolve, reject) => {
let rawData = '';
msg.on('data', chunk => { rawData += chunk });
msg.on('end', () => {
resolve(rawData);
});
});
})
.then(json => {
JSON.parse(json);
callback(json);
})
}
// to use this:
getJSON((your_json)=> {
// handling your json here.
})
You can use child_process to spawn a test server to provide JSON API. Example:
const { spawn } = require('child_process');
const expect = chai.expect;
const env = Object.assign({}, process.env, { PORT: 5000 });
const child = spawn('node', ['test-api.js'], { env });
child.stdout.on('data', _ => {
// Make a request to our app
getJSON((foo)=>{
// your asserts go here.
expect(foo).to.be.a('object');
expect(foo.some_attribute).to.be.a('string')
// stop the server
child.kill();
});
});
You can custom your someConfig variable in test environment to point to 'http://127.0.0.1:5000'. your test-api.js file is a simple nodejs script that always response an expected JSON for every request.
Updated unit test example

Mocha Unit Testing of Controller resolving promise coming from services

I have controller :
function(req, res) {
// Use the Domain model to find all domain
CIO.find(function(err, CIOs) {
if (err) {
response = responseFormat.create(false, "Error getting CIOs", err, {});
res.status(400).json(response);
} else {
var metrics = {
"count": CIOs.length
};
// .then means it will wait for it to finish, then let you have the result
var promises = [];
for (i in CIOs) {
promises.push(Analysis.structureMetrics(CIOs[i].toObject()))
}
var output = []
var errors = []
Q.allSettled(promises)
.then(function(results) {
for (i in results) {
if (results[i].state === "rejected") {
console.log(results[i])
errors.push(results[i].reason.errors)
output.push(results[i].reason)
} else {
output.push(results[i].value)
}
}
}).then(function() {
response = responseFormat.create(true, "List of all CIOs", output, metrics, errors);
res.status(200).json(response);
})
}
});
};
and cio.test file :
describe('/cio', function() {
describe('GET', function() {
//this.timeout(30000);
before(function() {
});
it('should return response', function(done) {
var response = http_mocks.createResponse({eventEmitter: require('events').EventEmitter})
var request = http_mocks.createRequest({
method: 'GET',
url: '/cio',
})
//var data = JSON.parse( response._getData() );
response.on('end', function() {
response.statusCode.should.be.equal(400);
done();
})
cioCtrl.getCIOs(request, response);
});
});
});
getting Error
Error: timeout of 10000ms exceeded. Ensure the done() callback is being called in this test
1>I have already tried increasing the time, but It doesn't work.
2> What I found is response.('end', function(){}) is not getting called, but not sure why
Any help would be appreciated.
Thanks!
Very good approach for unit testing is using the dependency injection.
For this, your controller file should be something like this:
module.exports = class MyController {
constructor(CIO) {
this._CIO = CIO;
this.handler = this.handler.bind(this);
}
handler(req, res) {
// your code here using this._CIO
}
};
Than in your main file, you create instance of controller:
const MyController = require('./controllers/my-controller');
// require your CIO, or create instance...
const CIO = require('./CIO');
const myController = new MyController(CIO);
You simply then pass instance of controller, or it's handler function to the place where it will be used.
Using this approach allows you to test well.
Assume your 'it' will look something like this:
it('should work', function(done) {
const fakeCIO = {
find: function() {
done();
}
};
const myController = new MyController(fakeCIO);
myController.handler();
});
Basic differences between testing techniques:
unit test - you test one single unit, how it calls functions, makes assignments, returns values
integration test - you add database to your previous test and check how it is stored/deleted/updated
end-to-end test - you add API endpoint to previous integration test and check how whole your flow works
Update:
Using async/await approach you will be able to test more things using your controller. Assume modifying it in something like this:
async function(req, res) {
try {
const CIOs = await CIO.find();
const metrics = {
"count": CIOs.length
};
const promises = CIOs.map(el => Analysis.structureMetrics(el.toObject());
for(const promise of promises) {
const result = await promise();
// do whatever you need with results
}
} catch(err) {
const response = responseFormat.create(false, "Error getting CIOs", err, {});
res.status(400).json(response);
}
Using such approach, during unit testing you can also test that your controller calls methods:
responseFormat.create
Analysis.structureMetrics
res.status
res.json
test catch branch to be executed
All this is done with fake objects.
And sure using OOP is not mandatory, it's just a matter of habits, you can do the same using functional style, with closures, for example.

Unit Testing redux async function with Jest

I'm pretty new to unit testing so please pardon any noobness.
I have a file api.js which has all the API call functions for the app. Each function returns its promise. Here's how it looks:
api.js
const api = {
getData() {
return superagent
.get(apiUrl)
.query({
page: 1,
});
},
}
Now coming to the redux async action that i'm trying to test. It looks something like this:
getDataAction.js
export function getData(){
return dispatch => {
api.getData()
.end((err, data) => {
if (err === null && data !== undefined) {
console.log(data);
} else if (typeof err.status !== 'undefined') {
throw new Error(`${err.status} Server response failed.`);
}
});
}
}
Now, In my test file, I've tried this:
getDataAction.test.js
jest.mock('api.js');
describe('getData Action', () => {
it('gets the data', () => {
expect(store.dispatch(getData())).toEqual(expectedAction);
});
});
This, throws me an error:
TypeError: Cannot read property 'end' of undefined
What am I doing wrong ? Now i'm able to mock api.js with default automocker of Jest, but how do I handle the case of running callback function with end ? Thanks a lot for any help !
Your mock of api needs to return a function that returns an object that has the end function:
import api from 'api' //to set the implantation of getData we need to import the api into the test
// this will turn your api into an object with the getData function
// initial this is just a dumb spy but you can overwrite its behaviour in the test later on
jest.mock('api.js', ()=> ({getData: jest.fn()}));
describe('getData Action', () => {
it('gets the data', () => {
const result = {test: 1234}
// for the success case you mock getData so that it returns the end function that calls the callback without an error and some data
api.getData.mockImplementation(() => ({end: cb => cb(null, result)}))
expect(store.dispatch(getData())).toEqual(expectedAction);
});
it('it thows on error', () => {
// for the error case you mock getData so that it returns the end function that calls the callback with an error and no data
api.getData.mockImplementation(() => ({end: cb => cb({status: 'someError'}, null)}))
expect(store.dispatch(getData())).toThrow();
});
});

Categories

Resources