I'm trying to stub function called on one of my routes in express Router with request from supertest library. I see that function foo is called correctly, unfortunately it is not replaced by stub function I wrote in test. Code is written in ES6 and I'm using babel-register and babel-polyfill to make it work.
I run testing script using
./node_modules/mocha/bin/mocha server --timeout 10000 --compilers js:babel-register --require babel-polyfill --recursive
router.js
import {foo} from '../controller';
const router = new Router();
router.route(ROUTE).post(foo);
controller.js
export function foo(req, res) {
res.status(200).send({
ok: 'ok'
});
}
test.js
import request from 'supertest';
import sinon from 'sinon';
import {app} from 'app';
import * as controller from 'controller';
const agent = request.agent(app);
describe('Admin routes tests', () => {
it('Tests login admin route', async () => {
const bar = () => {
console.log('bar');
};
sinon.stub(controller, 'foo', bar);
const req = await agent
.post(ROUTE)
.set('Accept', 'application/json');
console.log(stub.calledOnce); // false
});
});
Any help would be much appreciated.
Here is the solution:
app.js:
import express from "express";
import * as controller from "./controller";
const app = express();
app.post("/foo", controller.foo);
export { app };
controller.js:
export function foo(req, res) {
res.status(200).send({ ok: "ok" });
}
test.js:
import request from "supertest";
import sinon from "sinon";
import * as controller from "./controller";
import { expect } from "chai";
describe("Admin routes tests", () => {
it("Tests login admin route", (done) => {
const bar = () => {
console.log("bar");
};
const fooStub = sinon.stub(controller, "foo").callsFake(bar);
const { app } = require("./app");
const agent = request.agent(app);
agent
.post("/foo")
.set("Accept", "application/json")
.timeout(1000)
.end((err, res) => {
sinon.assert.calledOnce(fooStub);
expect(res).to.be.undefined;
expect(err).to.be.an.instanceof(Error);
done();
});
});
});
Since you stub foo controller and its return value is undefined. For supertest, there is no response, something like res.send(), the server will hang until the mocha test timeout and it will cause test failed.
.mocharc.yml:
recursive: true
require: ts-node/register
timeout: 2000
diff: true
inline-diffs: true
We add .timeout(1000) for supertest, because we know it will timeout(You stub the foo controller with bar). And, make an assertion for this timeout error.
Integration test result with coverage report:
Admin routes tests
bar
✓ Tests login admin route (1341ms)
1 passing (1s)
---------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
---------------|----------|----------|----------|----------|-------------------|
All files | 95.65 | 100 | 80 | 95.65 | |
app.ts | 100 | 100 | 100 | 100 | |
controller.ts | 50 | 100 | 0 | 50 | 2 |
test.ts | 100 | 100 | 100 | 100 | |
---------------|----------|----------|----------|----------|-------------------|
Source code: https://github.com/mrdulin/mocha-chai-sinon-codelab/tree/master/src/stackoverflow/41600031
Related
In my controller I have imported the secure util file when I call that util with path as parameter it returns unique Id. but how to call this function in test file using proxyquire and stub.
controller.ts
import { getSecret } from './util/secrect-util'
export function getId(req: Request, res: Response) {
const path='test/path'
const uniueID = getSecret(path);
console.log(uniueID) // prints testUser01
const url=`https://mytest.com?userid=${uniueID}`;
res.redirect(302,url);
}
test.ts
import { Request, Response } from "express";
import * as sinon from 'sinon';
import * as proxyquire from 'proxyquire';
describe('should redirect', () => {
const validurl:string="https://mytest.com?userid=testUser01";
let res:any;
let req:any
beforeEach(() => {
res = {
redirect:sinon.stub()
}
});
it('should get error with invalid path', () => {
const secPath = sinon.stub().returns('/test/invalidPath');
const urlctl = proxyquire('./controller', {
getSecret: { path: secPath },
});
urlctl.getId(req, res);
sinon.assert.calledWithExactly(
res.redirect,
400,
'inValidpath',
)
});
});
getting error while run the test cases. Please assist.
From the doc:
proxyquire({string} request, {Object} stubs)
request: path to the module to be tested e.g., ../lib/foo
stubs: key/value pairs of the form { modulePath: stub, ... }
module paths are relative to the tested module not the test file
therefore specify it exactly as in the require statement inside the tested file
values themselves are key/value pairs of functions/properties and the appropriate override
E.g.
controller.ts:
import { getSecret } from './util/secrect-util';
import { Request, Response } from 'express';
export function getId(req: Request, res: Response) {
const path = 'test/path';
const uniueID = getSecret(path);
console.log(uniueID);
const url = `https://mytest.com?userid=${uniueID}`;
res.redirect(302, url);
}
util/secrect-util.ts:
export function getSecret(path) {
return 'real implementation';
}
controller.test.ts:
import sinon from 'sinon';
import proxyquire from 'proxyquire';
describe('70666283', () => {
it('should pass', () => {
const getSecretStub = sinon.stub().returns('123');
const urlctl = proxyquire('./controller', {
'./util/secrect-util': {
getSecret: getSecretStub,
},
});
const req = {};
const res = { redirect: sinon.stub() };
urlctl.getId(req, res);
sinon.assert.calledWithExactly(getSecretStub, 'test/path');
sinon.assert.calledWithExactly(res.redirect, 302, 'https://mytest.com?userid=123');
});
});
Test result:
70666283
123
✓ should pass (1743ms)
1 passing (2s)
------------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
------------------|---------|----------|---------|---------|-------------------
All files | 88.89 | 100 | 50 | 88.89 |
70666283 | 100 | 100 | 100 | 100 |
controller.ts | 100 | 100 | 100 | 100 |
70666283/util | 50 | 100 | 0 | 50 |
secrect-util.ts | 50 | 100 | 0 | 50 | 2
------------------|---------|----------|---------|---------|-------------------
In controller I am redirecting to another url with query params as a state. State is nothing but random UUID. Like given below the controller.
test.controller.ts
import {Request,Response} from "express";
import {v4 as uuid} from "uuid";
export function test(req:Request,res:Response){
const state=uuid();
console.log(state) // 110ec58a-a0f2-4ac4-8393-c866d813b8d1
if(true){
const url=`https://testurl/user?state=${state}`;
res.redirect(302,url);
}
}
and the unit test file like below
test.controller.spec.ts
import {Request,Response} from "express";
import * as sinon from 'sinon';
import {expect} from 'chai';
import * as proxyquire from 'proxyquire';
descripbe('shoud redirect',()=>{
const validurl:string="https://testurl/user?state=110ec58a-a0f2-4ac4-8393-c866d813b8d1";
let res:any;
let req:any
let resstub:{calledWith:any};
beforeEach(()=>{
res={
redirect:()=>{}
}
resstub = sinon.stub(res,'redirect');
});
it('should redirect with valid url',()=>{
const urlctl=proxyquire('test-contriller',{});
urlctl.test(req,res);
expect(resstub.calledWith(302,validurl)).to.be.true;
})
})
when I validate the redirectUrl part it's getting failed because the state value is chaining every time randomly. Can please assist how to do I validate
From the doc How to use Link Seams with CommonJS. We will be using proxyquire to construct our seams so that we can stub the v4 function exported from the uuid package.
controller.ts:
import { Request, Response } from 'express';
import { v4 as uuid } from 'uuid';
export function test(req: Request, res: Response) {
const state = uuid();
if (true) {
const url = `https://testurl/user?state=${state}`;
res.redirect(302, url);
}
}
controller.test.ts:
import sinon from 'sinon';
import proxyquire from 'proxyquire';
describe('shoud redirect', () => {
let res: any;
let req: any;
beforeEach(() => {
res = { redirect: sinon.stub() };
});
it('should redirect with valid url', () => {
const uuidv4Stub = sinon.stub().returns('110ec58a-a0f2-4ac4-8393-c866d813b8d1');
const urlctl = proxyquire('./controller', {
uuid: { v4: uuidv4Stub },
});
urlctl.test(req, res);
sinon.assert.calledWithExactly(
res.redirect,
302,
'https://testurl/user?state=110ec58a-a0f2-4ac4-8393-c866d813b8d1',
);
});
});
test result:
shoud redirect
✓ should redirect with valid url (1592ms)
1 passing (2s)
---------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
---------------|---------|----------|---------|---------|-------------------
All files | 100 | 50 | 100 | 100 |
controller.ts | 100 | 50 | 100 | 100 | 6
---------------|---------|----------|---------|---------|-------------------
I have my internal function
//in greatRoute.ts
async function _secretString(param: string): Promise<string> {
...
}
router
.route('/foo/bar/:secret')
.get(
async (...) => {
...
const secret = _secretString(res.params.secret);
...
},
);
export default {
...
_secretString
};
and now I'm trying to mock the call with sinon.stub like this:
sinon.stub(greatRoute, '_secretString').resolves('abc');
But that doesn't work the way I want it to. When i call the route in my test it still goes into the _secretString function. Am I missing something here? I already tried to put the export in front of the function header like this:
export async function _secretString(param: string): Promise<string>
instead of doing the export default {...} but that didn't help.
You can use rewire package to stub _secretString function. E.g.
index.ts:
async function _secretString(param: string): Promise<string> {
return 'real secret';
}
async function route(req, res) {
const secret = await _secretString(req.params.secret);
console.log(secret);
}
export default {
_secretString,
route,
};
index.test.ts:
import sinon from 'sinon';
import rewire from 'rewire';
describe('61274112', () => {
it('should pass', async () => {
const greatRoute = rewire('./');
const secretStringStub = sinon.stub().resolves('fake secret');
greatRoute.__set__('_secretString', secretStringStub);
const logSpy = sinon.spy(console, 'log');
const mReq = { params: { secret: '123' } };
const mRes = {};
await greatRoute.default.route(mReq, mRes);
sinon.assert.calledWithExactly(logSpy, 'fake secret');
sinon.assert.calledWith(secretStringStub, '123');
});
});
unit test results with coverage report:
fake secret
✓ should pass (1383ms)
1 passing (1s)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 75 | 100 | 50 | 75 |
index.ts | 75 | 100 | 50 | 75 | 2
----------|---------|----------|---------|---------|-------------------
I'm having issues with Jest with Express and Supertest with only one test to create an user. If the database is clean it works but the console displays the following:
which makes posttest, what undoes all the migrations, not to be executed.
The full warning message is:
Jest did not exit one second after the test run has completed.
This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with --detectOpenHandles to troubleshoot this issue.
I've attempted to use done(), but the not only the problem of hanging persists, it doesn't show what's the problem, like without done(). Even a simple sum assertion will make Jest hang.
I don't have much to show, it is a simple test yet so many trouble. Maybe if you need more info, this is the repository.
Here is a minimal working example:
app.js:
import express, { Router } from 'express';
class App {
constructor() {
this.server = express();
this.middlewares();
this.routes();
this.exceptions();
}
middlewares() {
this.server.use(express.json());
}
routes() {
const routes = new Router();
routes.post('/api/users', (req, res) => {
res.status(201).json({ user: {} });
});
this.server.use(routes);
}
exceptions() {
this.server.use(async (err, req, res, next) => {
return res.status(500).send(err);
});
}
}
export default new App().server;
app.test.js:
import app from './app';
import request from 'supertest';
describe('User', () => {
it('Should create a new user', async () => {
const response = await request(app)
.post('/api/users')
.send({
first_name: 'Lorem',
last_name: 'Ipsum',
email: 'lorem#ipsum.com',
password: '12345678',
});
expect(response.status).toBe(201);
expect(response.body).toHaveProperty('user');
});
});
Functional test result with coverage report:
☁ jest-codelab [master] ⚡ npx jest --coverage --verbose /Users/ldu020/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/57897576/app.test.js
PASS src/stackoverflow/57897576/app.test.js
User
✓ Should create a new user (40ms)
----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files | 90 | 100 | 75 | 94.44 | |
app.js | 90 | 100 | 75 | 94.44 | 26 |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 4.617s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/57897576
I am using supertest for writing integration-tests for POST Api: api/v1/students?id="someId" which should throw a 4xx code if the id is already present.
import request from "supertest";
import { applyMiddleware, applyRoutes } from "../../src/utils";
import routes from "../../src/routes";
import errorHandlers from "../../src/middleware/errorHandlers";
describe("student routes", () => {
let router: Router;
beforeEach(() => {
router = express();
applyRoutes(routes, router);
applyMiddleware(errorHandlers, router);
});
afterEach(function () {
});
test("Create student", async () => {
const response = await request(router).post("/api/v1/students?id=lee");
expect(response.status).toEqual(201);
});
test("Create duplicate student test", async () => {
const response1 = await request(router).post("/api/v1/students?id=lee");
const response2 = await request(router).post("/api/v1/students?id=lee");
expect(response2.status).toEqual(409);
});
});
The problem is the first and second tests are not independent. The student with id lee created in the first test is already present when the second test is run. I want to reset the express() and make the tests independent of each other. How shall I proceed?
You should clear the data in memory db after each unit test case.
E.g.
app.ts:
import express, { Router } from 'express';
const app = express();
const router = Router();
export const memoryDB: any = {
students: [],
};
router.post('/api/v1/students', (req, res) => {
const studentId = req.params.id;
const foundStudent = memoryDB.students.find((student) => student.id === studentId);
if (foundStudent) {
return res.sendStatus(409);
}
memoryDB.students.push({ id: studentId });
res.sendStatus(201);
});
app.use(router);
export default app;
app.test.ts:
import request from 'supertest';
import app, { memoryDB } from './app';
describe('student routes', () => {
afterEach(() => {
memoryDB.students = [];
});
test('Create student', async () => {
const response = await request(app).post('/api/v1/students?id=lee');
expect(response.status).toEqual(201);
});
test('Create duplicate student test', async () => {
const response1 = await request(app).post('/api/v1/students?id=lee');
const response2 = await request(app).post('/api/v1/students?id=lee');
expect(response2.status).toEqual(409);
});
});
Integration test result with 100% coverage:
PASS src/stackoverflow/57565585/app.test.ts
student routes
✓ Create student (34ms)
✓ Create duplicate student test (17ms)
----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
app.ts | 100 | 100 | 100 | 100 | |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 4.276s, estimated 9s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/57565585