Sinon Stub on MongoDB FindById - node.js

I have an endpoint like this:
Router.ts
router.get('/endpoint', async (req: Request, res: Response, next: NextFunction) => {
const myId = req.params.myId;
const data = await getData(myId);
});
controller.ts
export async function getData(myId: string): Promise<ICollectionDocument> {
return myModel.findById(myId).lean(); // pay attention to .lean()
}
It is working as expected, but when I try to write a test for this, I get:
.findById(...).lean is not a function",
Test
import * as request from 'supertest';
import * as sinon from 'sinon';
import * as chai from 'chai';
import * as express from 'express';
import * as applicationSetup from '../src/app'
import * as db from '../src/db';
import { User } from ''../src/models/user;
describe('Request Handler', () => {
let UserStub: sinon.stub;
let app: express.Application;
before(async () => {
UserStub = sinon.stub(User, 'findOne');
app = await applicationSetup.init();
await db.init();
});
it('Should do something', async() => {
// Also tried with and without this piece of code
UserStub.returns({
exec: sinon.stub().resolves(),
});
const resp = await request(app)
.get('/endpoint')
.send()
resp.status.should.be.equal(200);
})
});

Related

How to stub async module dependencies for jest

Let's say I have the following route:
import {example} from '../some/place'
const processRequest = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try {
console.log('in route handler')
const foo = await example(req.body.someData)
res.status(200).send()
} catch (e) {
next(e)
}
}
export { processRequest }
And then I write a test like so:
import * as exampleModule from '../some/place'
import { processRequest } from '../routes/processRequest'
import sinon from 'sinon'
sinon.stub(exampleModule, 'example').returns(new Promise(() => {
return someData
})
describe('example test', () => {
it('example test', async () => {
await processRequest(mockRequest, mockResponse, mockNext)
})
})
This fails with:
thrown: "Exceeded timeout of 5000 ms for a test.
Use jest.setTimeout(newTimeout) to increase the timeout value, if this is a long-running test."
Through console.logs, I have verified the following:
The original implementation of example is not called
ProcessRequest is called and prints to console
The line in the route after const foo = await example(req.body.someData) is never called and nothing is printed, suggesting that somehow the promise return from my stub is incorrect. For the life of me I do not know how.
Any advice on the best way to stub async dependencies for jest tests would be super welcome!
An working example:
processRequest.ts:
import { NextFunction, Response, Request } from 'express';
import { example } from './someplace';
const processRequest = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try {
console.log('in route handler');
const foo = await example(req.body.someData);
console.log('foo: ', foo);
res.status(200).send();
} catch (e) {
next(e);
}
};
export { processRequest };
someplace.ts:
export const example = async (arg) => 'real data';
processRequest.test.ts:
import * as exampleModule from './someplace';
import { processRequest } from './processRequest';
import type { Response, Request } from 'express';
import sinon from 'sinon';
describe('example test', () => {
it('example test', async () => {
const exampleStub = sinon.stub(exampleModule, 'example').resolves('test data');
const mockRequest = {
body: { someData: 'a' },
};
const mockResponse = {
status: sinon.stub().returnsThis(),
send: sinon.stub(),
};
const mockNext = sinon.stub();
await processRequest((mockRequest as unknown) as Request, (mockResponse as unknown) as Response, mockNext);
sinon.assert.calledWithExactly(exampleStub, 'a');
sinon.assert.calledWithExactly(mockResponse.status, 200);
sinon.assert.calledOnce(mockResponse.send);
});
});
Test result:
PASS stackoverflow/74944946/processRequest.test.ts (7.98 s)
example test
✓ example test (16 ms)
console.log
in route handler
at stackoverflow/74944946/processRequest.ts:6:13
console.log
foo: test data
at stackoverflow/74944946/processRequest.ts:8:13
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 8.442 s, estimated 9 s
package versions:
"jest": "^26.6.3",
"sinon": "^15.0.1",

How to mock firestore query with mocha and sinon?

W.r.t. How to mock firestore with mocha how do I mock the following firestore query using sinon?
import * as admin from "firebase-admin";
const db: FirebaseFirestore.Firestore = admin.firestore();
const storeSnapshot: = await db
.doc(`/Client/${clientId}/Store/${storeId}`)
.get();
I tried:
import * as _sinon from 'sinon';
it('Query Client collection not empty test', async () => {
const clientStoreDocStub = _sinon.stub(db, "doc");
clientStoreDocStub.withArgs("/Client/123/Store/789").resolves({
id: "789",
settings: [ {setting1: "Setting1-value", setting2: "Setting2-value"}, {setting1: "Setting3-value", setting2: "Setting4-value"}]
});
clientStoreDocStub.withArgs("/Client/456/Store/012").resolves({
id: "012",
settings: [ {setting1: "Setting5-value", setting2: "Setting6-value"}, {setting1: "Setting7-value", setting2: "Setting8-value"}]
});
const storeSnapshot: FirebaseFirestore.DocumentSnapshot = await db
.doc("/Client/123/Store/789")
.get();
const store = storeSnapshot.data();
});
but get the following error:
1) Mock firebase firestore Tests
Query Client collection not empty test:
TypeError: dbInit_1.db.doc(...).get is not a function
import * as _chai from "chai";
import * as chaiAsPromised from "chai-as-promised";
import * as admin from "firebase-admin";
import * as _sinon from 'sinon';
_chai.use(chaiAsPromised);
var expect = _chai.expect;
const db: FirebaseFirestore.Firestore = admin.firestore();
describe("Test with mock firestore", () => {
var docRefStub;
beforeEach(() => { docRefStub = _sinon.stub(db, "doc");});
afterEach(() => {db.doc.restore();});
it("Test should pass with valid request params", async () => {
const expectedString = "Hello World!!!";
const testCollection = {
"/Client/123/Store/789": {
data: 1,
moreData: "Hello World!!!"
}
}
const setSpy = _sinon.spy();
docRefStub.callsFake(fakeFsDoc(testCollection, setSpy));
await callAFunction("123", "789");
expect(setSpy.called).to.be.true;
expect(setSpy.getCall(0).args[0].data).to.not.be.null;
expect(setSpy.getCall(0).args[0].moreData).to.deep.equal(expectedString);
}
});
export function fakeFsDoc(database, setSpy = null) {
return docId => {
const data = database[docId] ?? undefined;
return {
get: async () => ({
data: () => data
}),
set: setSpy
}
}
}

Testing multiple routes causes Jest mocked function to be undefined

I would like to know the best way to test multiple routes in expressJS with jest.
My current problem is when I try testing multiple routes that uses same mocked function, the last route throws an undefined error.
Here's the context in code.
app.ts
import * as express from "express";
import controllerOne from "./controllers/one";
import controllerTwo from "./controllers/two";
const app = express();
app.use("/one", controllerOne);
app.use("/two", controllerTwo);
export default app;
controllers/one.ts
import getDbConnection from "../db";
import * as express from "express";
const router = express.Router();
const db = getDbConnection();
router.get("/", async (_, res) => {
const sql = "SELECT * from table";
const result = await db.query(sql);
res.status(200).json(result);
});
export default router;
controllers/two.ts
import getDbConnection from "../db";
import * as express from "express";
const router = express.Router();
const db = getDbConnection();
router.get("/", async (_, res) => {
const sql = "SELECT * from table";
const result = await db.query(sql);
res.status(200).json(result);
});
export default router;
tests/one.ts
import * as request from "supertest";
import { mocked } from "ts-jest/utils";
import db from "../db";
jest.mock("../db");
const mockedDB = mocked(db);
const data = ["data"];
describe("Controller One", () => {
afterAll(() => {
jest.restoreAllMocks();
jest.resetModules();
});
beforeEach(() => {
const mockedQueryFn = {
query: jest.fn().mockResolvedValueOnce(data),
};
mockedDB.mockReturnValueOnce(mockedQueryFn);
});
it("should retrieve one", async () => {
const mod = require("../src/app");
const app = (mod as any).default;
await request(app)
.get("/one")
.expect(200)
.expect(function (res) {
expect(res.body).toEqual(data);
});
});
});
tests/two.ts
import * as request from "supertest";
import { mocked } from "ts-jest/utils";
import db from "../db";
jest.mock("../db");
const mockedDB = mocked(db);
const data = ["data"];
describe("Controller One", () => {
afterAll(() => {
jest.restoreAllMocks();
jest.resetModules();
});
beforeEach(() => {
const mockedQueryFn = {
query: jest.fn().mockResolvedValueOnce(data),
};
mockedDB.mockReturnValueOnce(mockedQueryFn);
});
it("should retrieve one", async () => {
const mod = require("../src/app");
const app = (mod as any).default;
await request(app)
.get("/two")
.expect(200)
.expect(function (res) {
expect(res.body).toEqual(data);
});
});
});
Now my problem is if I run the test, test/one.ts would pass test/two.ts would fail with the error TypeError: Cannot read property 'query' of undefined referencing this line const result = await db.query(sql)
When I switch up the controller imports
import controllerTwo from "./controllers/two";
import controllerOne from "./controllers/one";
then test/two.ts passes and test/one.ts fails with same error.
I would like to also mention that with/without reseting jest mock, I get same result.
It may have to do with the fact that you’re not calling done at the end of your test. Inside the expect part of the test, call done(). You also need to change the callback so that it like this:
it("should retrieve one", async (done) => {

mockResolvedValue returns undefined

I want to test the function below using the tests below. However, I fail in mocking the userService.login function, which always returns undefined instead of desired value. I have tried these approaches https://jestjs.io/docs/mock-functions#mock-return-values (please see the tests), but nothing seems to work.
FUNCTION
static async login(req: Request, res: Response, next: NextFunction): Promise<void> {
const userDTO = req.body;
try {
const accessToken = await userService.login(userDTO);
// accessToken is undefined here :(
res.setHeader('authorisation', `Bearer ${accessToken}`);
res.send('Logged in');
} catch (e) {
// eslint-disable-next-line callback-return
next(e);
}
}
TESTS:
import UserService from '#services/users.service';
import UserController from '#routers/controller/users.controller';
import { NextFunction, Request, Response } from 'express';
import UserRepository from '#data-access/user.repository';
jest.mock('#services/users.service');
let userService: UserService;
const fakeToken = 's$sdfDgf45d';
beforeAll(() => {
userService = new UserService(UserRepository);
// userService.login = jest.fn().mockResolvedValue('s$sdfDgf45d').mockRejectedValue('error');
});
describe('UserController: ', () => {
const mockReq = {
body: {
login: 'Artyom',
password: 'qwerty123'
}
};
const mockRes = {
setHeader: jest.fn(),
send: jest.fn()
};
const nextFunction = jest.fn();
test('the class constructor should have been called', () => {
expect(UserService).toHaveBeenCalled();
});
test('should send auth token in case of success scenario', async () => {
// #ts-ignore
userService.login.mockResolvedValue('s$sdfDgf45d').mockRejectedValue('error');
// jest.spyOn(userService, 'login').mockResolvedValue('s$sdfDgf45d' as any).mockRejectedValue('error');
await UserController.login(mockReq as Request, mockRes as any as Response, nextFunction as NextFunction);
// that's the test that fails
expect(mockRes.setHeader.mock.calls[0]).toEqual(['authorisation', `Bearer ${fakeToken}`]);
expect(mockRes.send.mock.calls[0][0]).toEqual('Logged in');
});
test('should throw an error', async () => {
return expect((userService.login as jest.Mock)()).rejects.toMatch('error');
});
});
ERROR:
Array [
"authorisation",
- "Bearer s$sdfDgf45d",
+ "Bearer undefined",
CODE:
https://github.com/lion9/node-express

Test NextJS API middleware with Jest

I have an API route with middleware setup in NextJS like so:
/src/middleware/validateData/index.ts
import { NextApiRequest, NextApiResponse } from 'next';
import schema from './schema';
type Handler = (req: NextApiRequest, res: NextApiResponse) => void;
export default (handler: Handler) => {
return (req: NextApiRequest, res: NextApiResponse) => {
const { error } = schema.validate(req.body, { abortEarly: false });
if (error) res.status(400).send(error);
else handler(req, res);
};
};
/src/api/foo.ts
import { NextApiRequest, NextApiResponse } from 'next';
import validateData from '../../middleware/validateData';
const foo = (req: NextApiRequest, res: NextApiResponse) => {
res.send('It works!');
};
export default validateData(foo);
The schema reference is a #hapi/joi schema to validate the req.body data and I haven't included it because I don't think it's relevant to the question.
I'm wondering how I can unit test the middleware on it's own? This is about as far as I got:
/src/middleware/validateData/index.test.ts
import validateData from './validateData';
describe('validateData', () => {
const mockHandler = jest.fn();
const mockReq = {
body: '',
};
const mockRes = {
send: jest.fn(),
};
it('responds with error', () => {
validateData(mockHandler)(mockReq, mockRes);
expect(mockRes.send).toHaveBeenCalled();
});
});
But with this technique I firstly get type errors that mockReq and mockRes are missing properties (so I guess I need to mock those correctly but not sure how), and secondly the test fails because res.send is not called despite invalid body data being passed.
Anyone know how to mock and test this correctly?
I feel like my approach is totally wrong because I want to inspect the entire response (status code, specific message received and so on). Is the only approach to spin up a mock server and actually mock an api call or something?
You can use node-mocks-http package in your case like this
/src/middleware/validateData/index.test.ts
import httpMocks from 'node-mocks-http';
import { NextApiRequest, NextApiResponse } from 'next';
import validateData from './validateData';
describe('validateData', () => {
const mockHandler = jest.fn();
const mockReq = httpMocks.createRequest<NextApiRequest>();
const mockRes = httpMocks.createResponse<NextApiResponse>();
it('responds with error', () => {
validateData(mockHandler)(mockReq, mockRes);
expect(mockRes.send).toHaveBeenCalled();
});
});
next-test-api-route-handler is a package (disclaimer: I created!) that simplifies writing unit tests for Next API routes. It uses test-listen under the hood to generate real HTTP responses. For example:
import validateData from './validateData';
import { testApiHandler } from 'next-test-api-route-handler';
describe('validateData', () => {
it('responds with error', async () => {
await testApiHandler({
handler: validateData((_, res) => res.send('It works!')),
test: async ({ fetch }) => {
// Returns a real ServerResponse instance
const res = await fetch();
// Hence, res.status == 200 if send(...) was called above
expect(res.status).toBe(200);
// We can even inspect the data that was returned
expect(await res.text()).toBe('It works!');
}
});
});
});
This way, you can also directly examine the fetched response object in your tests. Better, your API route handlers will function identically to how they would in Next.js since they're passed actual NextApiRequest and NextApiResponse instances instead of TypeScript types or mocks.
More examples can be found on GitHub.

Resources