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",
Related
I have a test 'should return 201 response code' which is failing:
Expected: 201
Received: 200
orderController.ts
import { Request, Response } from "express";
import { Orders } from "../models/orders";
const createOrder = async ( req: Request, res: Response) => {
try {
const newOrder = await Orders.create(req.body)
res.status(201).json({
status: 'success',
data: {
order: newOrder,
}
})
} catch (error) {
res.status(400).json({
status: 'fail',
message: error,
});
}
}
orderController.test.ts
import { Request, Response } from "express";
import { createOrder } from "../../controllers/orderController";
import { Orders } from "../../models/orders";
import httpMocks from 'node-mocks-http'
import newOrder from '../mock-data/new-order.json'
Orders.create = jest.fn()
let req: Request, res: Response
beforeEach(() => {
req = httpMocks.createRequest()
res = httpMocks.createResponse()
})
describe("createOrder", () => {
it("should have a createOrder function", () => {
expect(typeof createOrder).toBe("function");
})
it("should call Orders.create", () => {
req.body = newOrder
createOrder(req, res)
expect(Orders.create).toBeCalledWith(newOrder)
})
it("should return 201 response code", () => {
req.body = newOrder;
createOrder(req, res);
expect(res.statusCode).toBe(201);
})
})
So in terminal getting:
✕ should return 201 response code (1 ms)
● createOrder › should return 201 response code
expect(received).toBe(expected) // Object.is equality
Expected: 201
Received: 200
However if manually create an order in postman shows status: 201 Created.
I have changed even to a more simple orderController.ts like:
const createOrder = async (req: Request, res: Response) => {
const newOrder = await Orders.create(req.body)
res.status(201)
}
but getting the same result, cannot pass 'should return 201 response code'. Any ideas?
Just a guess:
200 is the default status code of your mocked response.
createOrder is async.
when you assert the status code to be 201, createOrder has not been run yet.
Try to await createOrder(...) in your test and see if this helps.
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);
})
});
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 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
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.