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
Related
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",
I'm using a middleware to verify token with this code:
import { Request, Response, NextFunction } from "express";
import jwt from "jsonwebtoken";
class VerifyToken {
public verify(req: Request, res: Response, next: NextFunction) {
try {
const authHeader = req.headers["authorization"];
const token = authHeader?.split(" ")[1];
const signature = process.env.JWT_SIGNATURE;
jwt.verify(token, signature);
next();
} catch (error) {
return res.status(401).json("Acess denied");
}
}
}
export default new VerifyToken().verify;
I'm using Jest to test this middleware, here's the code:
import dotenv from "dotenv";
import { NextFunction, Request, Response } from "express";
import verifyToken from "../../src/middlewares/verifyToken";
describe("Verify token", () => {
let mockRequest: Partial<Request>;
let mockResponse: Partial<Response>;
let nextFunction: NextFunction = jest.fn();
beforeAll(() => {
dotenv.config({ path: ".env" });
});
beforeEach(() => {
mockRequest = {};
mockResponse = {
json: jest.fn(),
};
});
it("should verify token with a invalid token", () => {
const token = process.env.TEST_FALSE_TOKEN;
mockRequest = {
headers: {
authorization: `bearer ${token}`,
},
};
verifyToken(mockRequest as Request, mockResponse as Response, nextFunction);
expect(mockResponse.status).toBe(401);
});
it("should verify token with a valid token", () => {
const token = process.env.TEST_TOKEN;
mockRequest = {
headers: {
authorization: `bearer ${token}`,
},
};
verifyToken(mockRequest as Request, mockResponse as Response, nextFunction);
expect(nextFunction).toBeCalledTimes(1);
});
});
When I run a test using Jest, it shows the following error:
TypeError: res.status is not a function
I've already tried to use ErrorRequestHandler with the request but I get the same error.
How can I fix this? Thanks for the help.
Your mocked response doesn't have a status function on it..
mockResponse = {
json: jest.fn(),
status: jest.fn().mockReturnThis(),
};
I am using mocha and sinon for test the node services, In controller I have getToken npm module for getting the token with name and value as parameters and in spec file I trying to send empty name as parameter using withargs but the response getting success excepted result is token creating fail please help on this issue.
controller.ts
import {getToken} from './tokenUtil';
export async function userInfo(req:Request,res:Response){
try{
let token = await getToken(name,value);
}
catch(error){
res.send({status:'Failue',message:error});
return
}
res.send({status:'success',message:'token creation success'})
}
tokenUtil.ts
export async function getToken(name,value){
// token code here
}
token.spce.ts
import * as sinon from 'sinon';
import * as proxyquire from 'proxyquire';
describe('get token',()=>{
let req:any;
let res:any;
beforeEach(()=>{
res={
send:sinon.stub();
}
it('getting error when given empty name',async()=>{
let tokenStub = sinon.stub().withArgs('',tokenValue).returns(undefined);
let tokenctl=proxyquire('./controller',{
'./tokenUtil':tokenStub
})
await tokenctl.userInfo(req,res);
sinon.assert.calledWithExactly(res.send,{status:'Failue',message:'token creating fail'})
})
})
})
You are testing the controller.ts module, so the test file name should be controller.spec.ts or controller.test.ts.
Since the ./tokenUtil use named exports, so the tokenStub should be an object.
You should use sinon.stub().rejects() to create a promise stub with rejected value.
E.g.
controller.ts:
import { getToken } from './tokenUtil';
import { Request, Response } from 'express';
export async function userInfo(req: Request, res: Response) {
const { name, value } = req.body;
try {
let token = await getToken(name, value);
res.send({ status: 'success', message: 'token creation success' });
} catch (error) {
res.send({ status: 'Failue', message: error });
}
}
tokenUtil.ts:
export async function getToken(name, value) {
// token code here
}
controller.test.ts:
import sinon from 'sinon';
import proxyquire from 'proxyquire';
describe('get token', () => {
let req: any;
let res: any;
beforeEach(() => {
res = {
send: sinon.stub(),
};
});
it('should create token success', async () => {
req = { body: { value: '123', name: 'teresa teng' } };
let tokenStub = {
getToken: sinon.stub().withArgs(req.body.name, req.body.value).resolves(),
};
let tokenctl = proxyquire('./controller', {
'./tokenUtil': tokenStub,
});
await tokenctl.userInfo(req, res);
sinon.assert.calledWithExactly(res.send, { status: 'success', message: 'token creation success' });
});
it('should handle error when given empty name', async () => {
const tokenValue = '123';
req = { body: { value: tokenValue, name: '' } };
const error = new Error('token creating fail');
let tokenStub = {
getToken: sinon.stub().withArgs('', tokenValue).rejects(error),
};
let tokenctl = proxyquire('./controller', {
'./tokenUtil': tokenStub,
});
await tokenctl.userInfo(req, res);
sinon.assert.calledWithExactly(res.send, { status: 'Failue', message: error });
});
});
Test result:
get token
✓ should create token success (101ms)
✓ should handle error when given empty name
2 passing (112ms)
So I've written a LoggingInterceptor for a Nest.js project.
This is my implementation
#Injectable()
export class LoggingInterceptor implements NestInterceptor {
constructor(private readonly logger: LoggingService) {}
intercept(
context: ExecutionContext,
next: CallHandler<any>,
): Observable<any> {
// Timestamp from start of request so we can calculate duration
const reqStartTime = getNanoSecTime()
const httpContext = context.switchToHttp()
const request = httpContext.getRequest<Request>()
return next.handle().pipe(
// If the response is successful, we'll log the HTTP response
map((data) => {
const response = httpContext.getResponse<Response>()
const logData = LoggingService.getLogData(
reqStartTime,
request,
response.statusCode,
)
this.logger.log('HTTP response completed', logData)
return data
}),
// If there is an error in the handler, we'll log the
// HTTP response with additional error metadata
catchError((err) => {
let statusCode: number
if (err instanceof HttpException) {
statusCode = err.getStatus()
} else {
statusCode = 500
}
console.log('statusCode', statusCode)
const logData = LoggingService.getLogData(
reqStartTime,
request,
statusCode,
)
this.logger.error('Error in handler', err, logData)
// We'll rethrow the error to be caught by Nest's built-in Exception Filter
throw err
}),
)
}
}
And this is my test setup
const mockLoggingService = sinon.createStubInstance(LoggingService)
const getLogDataStub = sinon
.stub(LoggingService, 'getLogData')
.returns(mockLogData)
const mockContext = {
switchToHttp: () => ({
getRequest: () => mockRequest,
getResponse: () => mockResponse,
}),
} as ExecutionContext
describe('LoggingInterceptor', () => {
let interceptor: LoggingInterceptor
beforeEach(() => {
interceptor = new LoggingInterceptor(mockLoggingService)
})
afterEach(() => sinon.restore())
it('call LoggingService.log with correct arguments', (done) => {
const mockHandler: CallHandler = {
handle: () => of([]),
}
interceptor.intercept(mockContext, mockHandler).subscribe({
next: () => {
expect(getLogDataStub.calledOnce).to.be.true
expect(mockLoggingService.log.calledOnce).to.be.true
expect(mockLoggingService.log.args[0][0]).to.equal(
'HTTP response completed',
)
expect(mockLoggingService.log.args[0][1]).to.equal(mockLogData)
},
complete: () => {
done()
},
})
})
it('should call LoggingService.error and handle when err is instance of HttpException', (done) => {
const error = new HttpException('Bad Gateway', 502)
const mockHandler: CallHandler = {
handle: () => throwError(() => error),
}
interceptor.intercept(mockContext, mockHandler).subscribe({
error: () => {
expect(getLogDataStub.calledOnce).to.be.true
expect(getLogDataStub.args[0][2]).to.equal(502)
expect(mockLoggingService.error.calledOnce).to.be.true
expect(mockLoggingService.error.args[0][0]).to.equal('Error in handler')
expect(mockLoggingService.error.args[0][1]).to.equal(error)
expect(mockLoggingService.error.args[0][2]).to.equal(mockLogData)
done()
},
})
})
I'm pretty new to Observables, so I'm really not sure why my test suite keeps on failing. Every test passes if run individually, but there is evidently some sort of memory leak occurring when running all tests, as the statusCode from the first test leaks into the statusCode for the 2nd and 3rd tests, always causing my assertions for the last tests to fail.
Well, it turns out the problem with my test had nothing to do with Observables and everything to do with where I was initializing my stubs.
If anyone's curious, I had to move my mockLoggingService into a beforeEachHook to make sure a new instance was being created before each test.
describe('LoggingInterceptor', () => {
let interceptor: LoggingInterceptor
let mockLoggingService
// originally, my mockLoggingService was being declared outside my test block
beforeEach(() => {
mockLoggingService = sinon.createStubInstance(LoggingService)
interceptor = new LoggingInterceptor(mockLoggingService)
})
//
I am trying to mock the axios's request method. But it throwing error
it('should execute axios request method once', async () => {
jest.mock('axios');
axios.request.mockImplementation(() =>
Promise.resolve({
data: {}
})
);
const requestObj = {
method: 'GET',
url: 'http://mock.url',
headers: {}
};
await request(requestObj);
expect(axios.request).toHaveBeenCalledTimes(1);
});
request.js
export default async (request, httpService = axios) => {
const { method, data, headers } = request;
let { url } = request;
const token = getLocalstorage('token');
if (token) {
headers.token = token;
}
if (method === 'GET') {
if (data) {
url += `?${serialize(data)}`;
}
}
return httpService
.request({
method,
url,
headers: Object.assign({}, headers),
...(method !== 'GET' && { data })
})
.then(successResponse, error => {
throwHttpError(error);
});
};
error
Here is the solution based on:
"jest": "^24.8.0",
"ts-jest": "^24.0.2",
"typescript": "^3.5.3"
"axios": "^0.19.0",
request.ts:
import axios from 'axios';
const serialize = data => data;
const getLocalstorage = key => key;
const successResponse = () => console.log('successResponse');
const throwHttpError = error => new Error(error);
export default async (request, httpService = axios) => {
const { method, data, headers } = request;
let { url } = request;
const token = getLocalstorage('token');
if (token) {
headers.token = token;
}
if (method === 'GET') {
if (data) {
url += `?${serialize(data)}`;
}
}
return httpService
.request({
method,
url,
headers: Object.assign({}, headers),
...(method !== 'GET' && { data })
})
.then(successResponse, error => {
throwHttpError(error);
});
};
Unit test, request.spec.ts
import request from './request';
import axios from 'axios';
jest.mock('axios');
describe('request', () => {
it('should execute axios request method once', async () => {
(axios.request as jest.Mock<any, any>).mockResolvedValueOnce({ data: 'mocked data' });
const requestObj = {
method: 'GET',
url: 'http://mock.url',
headers: {}
};
await request(requestObj);
expect(axios.request).toHaveBeenCalledTimes(1);
});
});
Unit test result:
PASS src/stackoverflow/57353897/request.spec.ts
request
✓ should execute axios request method once (13ms)
console.log src/stackoverflow/57353897/request.ts:4
successResponse
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 2.637s, estimated 3s