How to mock nested functions with Jest - node.js

I am trying to mock an HTTP resource .
In the controller, a function from an external module is invoked. I am able to invoke the controller. But unable to mock the external call inside it.
How to mock a function call inside a controller.
From the below example, I am able to invoke getData () from my app.test.js. But how to mock externalAPICall(param1,param2) inside this function?
Contoller.js
const { externalAPICall } = require('#/external-module');
const getData = async (req, res, next) => {
try {
const response = await externalAPICall(param1,param2)
.then(resp => resp.payload)
.catch(error => {
throw (error);
});
res.status(200).send(response);
} catch (error) {
next(error) //Calling next error handling middleware
}
};
module.exports = {
getData
}
app.test.js
const {Server} = require('../src/server');
const server = new Server();
const request = require("supertest");
describe('testAPI', () => {
const app = server.app
test("GET method response has status code = 200", async () => {
const response = await request(app).get("/api/app1")
.auth('user', 'password');
expect(response.statusCode).toBe(200);
});
});

Related

How to mock a knex function used in a route

I have this function that configure knex by environment
const knexConnection = () => {
const config = require('./connection')[environment];
return knex(config)
}
I use this function in my route.js
module.exports = (app) => {
app.get("/test", (req,res)=>{
knexConnection().raw("SELECT NOW() as time").then(result => {
const time = _.get(result.rows[0],'time')
res.send(time);
}).catch(err => throw(err))
})
}
my test file for the route.js
const sinon = require("sinon");
const chai = require("chai");
const mock = require('proxyquire')
const httpStatus = require('http-status');
const expect = chai.expect;
const myStub = sandbox.stub().resolves("Query executed")
const route = mock('../routes', {'../../knexConntection':knexConnection : { raw: myStub }}})
route(app)
chai.request(app)
.get('/test')
.set('content-type', 'application/x-www-form-urlencoded')
.end((err, res) => {
if (err) done(err);
expect(myStub).to.have.been.called;
expect(res.status).to.equal(200)
done();
})
When i execute the test file, the knexConnection.raw is stubbed and it shows the current time. and the test fails. it says the stub was never called.
I've been trying for days and it still hasnt work. any idea how to stub knex query?
UPDATE
After struggling with it for hours, I figured the stub get skipped because the app get instantiated before the stub. so the stub never get loaded.
My server structure has this structure.
-- server.js
//...all server stuff
//load all modeles routes using route
route(app)
here is my index.js as I dynamically load all route in server app.
var fs = require("fs");
module.exports = app => {
fs.readdirSync(__dirname).forEach(file => {
if (file == "index.js") return;
const name = file.substr(0, file.indexOf("."));
require("./" + name)(app);
});
};
My mock still is being skipped and app get called first.
You can't change raw as knexConnection is a function not an object.
knexConnection().raw(...).then(...)
That is, it is a function that returns an object which has a raw function on it.
Besides, we might as well stub knexConnection while we're at it. So we would have control over what raw is.
const promise = sinon.stub().resolves("Query executed")
const knexConnection = sinon.stub().returns({
raw: promise
})
Just one more thing, I've used Mocha. And to pass the stub from beforeEach to it, I use this.currentTest (in beforeEach) and this.test (in it). See the comments.
This made my tests passed:
// Import the dependencies for testing
const chai = require('chai');
const chaiHttp = require('chai-http');
const app = require('../server');
const route = require('../route');
const sinon = require("sinon");
const mock = require('proxyquire')
const httpStatus = require('http-status');
const expect = chai.expect;
chai.use(chaiHttp);
chai.should();
describe("test routes", () => {
beforeEach(function() {
const promise = sinon.stub().resolves("Query executed")
// runs before all tests in this block
const knexConnection = sinon.stub().returns({
raw: promise
})
this.currentTest.myStub = promise //so as to access this in 'it' with this.test.myStub
// warning : {'./knex': { knexConnection : knexConnection }} would replace knexConnection in route file
// with an object { knexConnection : knexConnection } causing the test to fail.
// Instead, you should write {'./knex': knexConnection}
const route = mock('../route', {'./knex': knexConnection})
route(app)
});
it("should call myStub", function(done) {
var myStub = this.test.myStub;
chai.request(app)
.get('/test')
.set('content-type', 'application/x-www-form-urlencoded')
.end((err, res) => {
if (err) done(err);
sinon.assert.called(myStub);
done();
})
})
it("should have 'Query executed' as text", function(done) {
var myStub = this.test.myStub;
chai.request(app)
.get('/test')
.set('content-type', 'application/x-www-form-urlencoded')
.end((err, res) => {
if (err) done(err);
sinon.assert.match(res.text, "Query executed")
done();
})
})
it("should have 200 as status", (done) => {
chai.request(app)
.get('/test')
.set('content-type', 'application/x-www-form-urlencoded')
.end((err, res) => {
if (err) done(err);
expect(res.status).to.equal(200)
done();
})
})
})
The route file:
const knexConnection = require('./knex.js');
module.exports = (app) => {
app.get("/test", (req,res)=>{
knexConnection().raw("SELECT NOW() as time").then(result => {
res.send(result);
}).catch(err => { throw(err) })
})
}
If you have any more questions, please do ask.

How to test next() in express middleware in JEST

After a lot of efforts i was not able to figure this one out and hence planned to get the help. I am using a middleware in my node+ express app which looks like :
import mainConfig from '../mainConfig/index';
const axios = require('axios');
module.exports = {
authHandler: (req, res, next) => {
return mainConfig.initialize().then(() => {
const apiUri = mainConfig.get('app.api');
if (apiUri) {
return axios.get(apiUri).then(response => {
next();
}).catch(error => {
res.redirect('/expired');
throw new Error(error);
});
}
}).catch(() => {
});
}
};
For this, I have written the test case in which I was able to mock the axios and my mainCongig module. Now, I want to test whether next() was called as the request was resolved for axios. Can someone help me with the same?
test case I have written is :
import mainConfig from '../mainConfig';
const axios = require('axios');
const middlewares = require('./check-auth');
jest.mock('axios');
describe('Check-Auth Token', () => {
it('should call the Sign In API when live Conf is initalized and have the API URL', () => {
mainConfig.get = jest.fn();
mainConfig.get.mockReturnValue('https://reqres.in/api/users');
mainConfig.initialize = jest.fn(() => Promise.resolve({ data: {} }));
const req = jest.fn(), res = { sendStatus: jest.fn() }, next = jest.fn();
axios.get.mockImplementation(() => Promise.resolve({ data: {} }));
middlewares.authHandler(req, res, next);
expect(next).toHaveBeenCalled(); // coming as not called.
});
});
You have to wait for the middleware to resolve. As you are returning a promise from your middleware, you can wait in the test with an await statement:
import mainConfig from '../mainConfig';
const axios = require('axios');
const middlewares = require('./check-auth');
jest.mock('axios');
describe('Check-Auth Token', () => {
it('should call the Sign In API when live Conf is initalized and have the API URL', async () => {
mainConfig.get = jest.fn();
mainConfig.get.mockReturnValue('https://reqres.in/api/users');
mainConfig.initialize = jest.fn(() => Promise.resolve({ data: {} }));
const req = jest.fn(), res = { sendStatus: jest.fn() }, next = jest.fn();
axios.get.mockImplementation(() => Promise.resolve({ data: {} }));
await middlewares.authHandler(req, res, next);
expect(next).toHaveBeenCalled(); // coming as not called.
});
});
Note that in order to be able to use the await keyword you need to define your test with async.
I'm not an expert, but as far as I know you are testing asynchronous code. So you have to use the done() keyword. Lookup this for more information: https://jestjs.io/docs/en/asynchronous

Jest/SuperTest Express integration tests - Can't set headers after they are sent. (when you call the same endpoint in multiple tests)

This one's killing me..
I'm writing integration tests for an Express (Typescript) app, using Jest and Supertest.
I have multiple tests for the same endpoint, to test responses from when a mocked service returns data correctly and when it rejects a promise with an Error object.
The tests run fine when each request() in each it() block hits a unique end point, but when endpoints are shared between blocks I get the following error:
Can't set headers after they are sent.
This is my test code:
let request = null;
let app = null;
const async = require('async');
import GError from '../../src/services/ErrorService';
const { list } = require('../../src/controllers/RecipeController');
let throwError: boolean = false;
let error = null;
const errorMsg: string = 'Something went wrong!';
const listData: Array<object> = [{id: 1, base: 'something'}];
jest.mock('../../src/services/RecipeService', () => {
return jest.fn().mockImplementation(() => ({
list: jest.fn(() => {
if (throwError) {
return Promise.reject(error);
}
return Promise.resolve(listData);
})
}));
});
beforeEach(() => {
request = require('supertest');
app = require('../../src/app');
});
afterEach( ( done ) => {
throwError = false;
error = null;
app.close( () => {
delete require.cache[require.resolve('../../src/app')];
done();
});
});
describe('Root Path', () => {
it('should return a welcome message', (done) => {
request(app)
.get('/')
.end((err, res) => {
expect(res.text).toEqual('Test API.');
expect(res.statusCode).toBe(200);
done();
});
});
});
describe('Recipe List', () => {
it('should call controller and return correct response when successful or error is thrown in service', (done) => {
const path: string = '/recipes/list';
request(app)
.get(path)
.end((err, res) => {
expect(JSON.parse(res.text)).toEqual({
recipes: listData
});
done();
});
});
it('should return an error response if service rejects promise', (done) => {
throwError = true;
error = new GError(errorMsg);
const path: string = '/recipes/list';
request(app)
.get(path)
.end((err, res) => {
expect(JSON.parse(res.text)).toEqual({
errors: {
message: errorMsg
}
});
done();
});
});
});
I think I need to reset the app in between tests, which is what I'm trying to achieve with:
beforeEach(() => {
request = require('supertest');
app = require('../../src/app');
});
But with no joy. Can anyone shine a light?
UPDATE:
Here's the controller method the route hits:
exports.list = async (req, res, next) => {
const recipes: IRecipeList = await recipeService.list().catch(err => {
return next(err);
});
const response: IRecipeListResponse = {recipes};
res.status(200).json(response);
};
SOLVED:
So it turned out to be nothing to do with Jest / Superagent (I was sure it was to do with one of these). Strangely though I only get this error in the context of running integration tests, there is no error when hitting the end point in Postman - which was super confusing.
PROBLEM:
exports.list = async (req, res, next) => {
const recipes: IRecipeList = await recipeService.list().catch(err => { . //this doesn't stop the execution past this await
return next(err);
});
//this still gets processed if the service rejects the promise
const response: IRecipeListResponse = {recipes};
res.status(200).json(response);
};
SOLUTION:
exports.list = async (req, res, next) => {
let error = false;
const recipes: IRecipeList = await recipeService.list().catch(err => {
error = true;
return next(err);
});
if (error) {
return;
}
const response: IRecipeListResponse = {recipes};
return res.status(200).json(response);
};
This error occurs when you send response more than once.

nodejs sinonjs stub on class construcor

I'm using nodejs exif library to retrieve metadata from JPEG files.
this lib is used this way :
import * as exif from 'exif'
new exif.ExifImage('path_to_file.jpg', function(err, metadata){ ... })
I've found everywhere how to stub a class method using sinon, pretty simple.
But I don't get how to stub this class constructor so that metadata (or err if I want to test failing case) will be the stubbed value I need to perform my test.
We can still use Sinon with callsFake function. Here is the example:
// src.js
const exif = require("exif");
function readImage() {
// convert to promise for easier testing
return new Promise((resolve, reject) => {
new exif.ExifImage("path_to_file.jpg", function(err, metadata) {
if (err) {
reject(err);
}
resolve(metadata);
});
});
}
module.exports = { readImage };
meanwhile for test
// test.js
const sinon = require('sinon');
const src = require('./src');
const exif = require('exif');
const expect = require('chai').expect;
describe('test exifimage', () => {
let exifStub;
beforeEach(function() {
exifStub = sinon.stub(exif, 'ExifImage')
})
afterEach(function() {
sinon.restore();
})
it('test when success', async () => {
const metadata = 'cinta';
// mock ExifImage as similar as its function signature
exifStub.callsFake((filename, callback) => {
callback(null, metadata); // error is set as null then we set metadata
});
const response = await src.readImage();
expect(response).to.equal(metadata);
});
it('test when error', async () => {
const errorMessage = 'this is error';
exifStub.callsFake((filename, callback) => {
callback(errorMessage); // specify error, error is defined as first param
});
try {
await src.readImage();
} catch (error) {
expect(error).to.equal(errorMessage);
}
});
});
Hope it helps

REST API unit test with promise return in nodejs

I have a function in a class which invokes REST api and returns Promise object.I am able to test Promise object bu I am not sure how we can stub or mock rest api call and test.
Token.js
class Token {
getToken(payload) {
let outahToken = new Promise(function (resolve, reject) {
request('hhtps://xyz.com', function (err, res, body) {
if (err) {
reject(err);
} else {
console.log(res);
resolve(body);
}
})
});
return outahToken;
}
}
module.exports = Token;
Token.test.js
'use strict'
const chai = require("chai");
const expect = chai.expect;
const nock = require('nock');
const sinon = require("sinon");
const Token = require('Token');
describe('Get User tests', () => {
let Token;
beforeEach(() => {
outhController = new Token();
sinon.stub(Token, 'getToken').returns(Promise.resolve({
name: "All"
}));
});
it('Outh test', (done) => {
Token.getToken(payload)
.then(response => {
expect(typeof response).to.equal('object');
done();
});
});
});
We can mock request module with proxyquire and check if it is being called with correct argument. We need proxyquire because request module export a function request() which harder to mock with Sinon only.
And because request method is a callback function, we can use Sinon yields to mock it.
const chai = require('chai');
const sinon = require('sinon');
const proxyquire = require('proxyquire');
const expect = chai.expect;
describe('Token test', function() {
let outhController;
let Token;
let requestStub;
beforeEach(() => {
const err = null;
const res = null;
const body = { name: 'All' };
requestStub = sinon.stub().yields(err, res, body); // create sinon for request function and return response body that we want
Token = proxyquire('Token', { request: requestStub }) // replace original request module with our sinon stub
outhController = new Token();
});
it('Outh test', (done) => {
const payload = {};
outhController.getToken(payload)
.then(response => {
sinon.assert.calledWith(requestStub, 'hhtps://xyz.com');
expect(typeof response).to.equal('object');
expect(response.name).to.equal('All');
done();
});
});
});
Hope it helps

Resources