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.
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 want to make a test that some addUser() method have been called and returned value. But i get this error: "No overload matches this call. Overload 1 of 4, '(object: typeof UserController, method: never): SpyInstance<never, never>', gave the following error.
So my userController.spec.ts where the error is in jest.spyOn(...):
import {UserController} from "../../src/modules/Users/Infrastructure/http/controllers";
describe('Users Controllers series ', function () {
it("should create a new user", async () => {
const spy = jest.spyOn(UserController, "addUser").mockReturnValueOnce()
})
});
and then userController.ts here
import { UserServices} from "../../../domain/services";
import {CreateUSerDTO} from "../../../domain/dto/createUserDTO";
import express from "express";
import { Response} from "../../../../../shared/helpers/response";
export class UserController {
constructor(private readonly userService: UserServices) {}
async addUser(req: express.Request, res: express.Response):Promise<Response<CreateUSerDTO>> {
let dto:CreateUSerDTO = req.body as CreateUSerDTO
dto = {
username:dto.username,
useremail: dto.useremail,
password: dto.password
}
try {
const result = await this.userService.createUser(dto)
console.log(res.json(result))
return Response.ok<CreateUSerDTO>(result)
}catch (error) {
return Response.fail<CreateUSerDTO>(error)
}
}
}
Let me know what's wrong in my code
Root cause of the issue is that you are configuring the spy on the UserController constructor while addUser is only available on UserController instances.
You should either spy on the concrete UserController instance or spy on the UserController prototype:
describe('Users Controllers series ', function () {
it("should create a new user", async () => {
const spy = jest.spyOn(UserController.prototype, "addUser").mockReturnValueOnce()
})
});
I currently have an issue with my code so while I am debugging i am trying to understand which approach shall I take in order to solve the issue.
I am using google oauth with passport.js. from client I have an action to fetch the authentication.
import axios from "axios";
const axiosInstance = axios.create({
baseURL: "http://localhost:3000/api",
timeout: 3000,
});
export const fetchUser = () => async (dispatch, getState, api) => {
try {
await axiosInstance.get("/auth/current_user").then((res) => {
dispatch({ type: FETCH_USER, payload: res.data });
});
} catch (e) {
console.log(e.message);
}
};
here is the client-side store set up:
const store = createStore(
reducers,
window.INITIAL_STATE, //
applyMiddleware(thunk)
);
and this is the server side store set up:
export default (req) => {
const store = createStore(reducers, {}, applyMiddleware(thunk));
console.log("store from servre", store);
return store;
};
Another approach would be instead of defining axiosInstance in action, i define it when I create the client side store and pass it to thunk.withExtraArgument iike this.
const axiosInstance = axios.create({
baseURL: "/",
});
const store = createStore(
reducers,
window.INITIAL_STATE,
applyMiddleware(thunk.withExtraArgument(axiosInstance))
);
I changed the action accordingly:
export const fetchUser = () => async (dispatch, getState, api) => {
try {
await api.get("/auth/current_user").then((res) => {
dispatch({ type: FETCH_USER, payload: res.data });
});
} catch (e) {
console.log(e.message);
}
};
My question which method should I use. CUrrently I have issue in both methods :) . But if i know which way is correct then I will solely focus on that method.
Thank You
I will suggest you go with with creating an instance of axios in a file and importing it whereever you need it, instead of adding it as an argument to thunk
api.js
import axios from "axios";
const axiosInstance = axios.create({
baseURL: "http://localhost:3000/api",
timeout: 3000,
});
export default axiosInstance;
actions.js
import api from '/path/to/api';
export const fetchUser = () => async (dispatch, getState) => {
try {
await api.get("/auth/current_user").then((res) => {
dispatch({ type: FETCH_USER, payload: res.data });
});
} catch (e) {
console.log(e.message);
}
};
The advantage of the above solution is that you can import and use the instance for making direct API calls too in your components for which you do not need to dispatch actions to the reducers.
I would like to log the incoming requests and outgoing responses for my API. I created a request interceptor and a response interceptor as described here
https://docs.nestjs.com/interceptors
So the request interceptor only logs the request object
#Injectable()
export class RequestInterceptor implements NestInterceptor {
private readonly logger: Logger = new Logger(RequestInterceptor.name, true);
public intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const { originalUrl, method, params, query, body } = context.switchToHttp().getRequest();
this.logger.debug({ originalUrl, method, params, query, body }, this.intercept.name);
return next.handle();
}
}
and the response interceptor waits for the outgoing response and logs the status code and response object later on
#Injectable()
export class ResponseInterceptor implements NestInterceptor {
private readonly logger: Logger = new Logger(ResponseInterceptor.name, true);
public intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const { statusCode } = context.switchToHttp().getResponse();
return next.handle().pipe(
tap((responseData: any) =>
this.logger.debug({ statusCode, responseData }, this.intercept.name),
),
);
}
}
I would like to test them but unfortunately have almost no experience in testing. I tried to start with the request interceptor and came up with this
const executionContext: any = {
switchToHttp: jest.fn().mockReturnThis(),
getRequest: jest.fn().mockReturnThis(),
};
const nextCallHander: CallHandler<any> = {
handle: jest.fn(),
};
describe('RequestInterceptor', () => {
let interceptor: RequestInterceptor;
beforeEach(() => {
interceptor = new RequestInterceptor();
});
describe('intercept', () => {
it('should fetch the request object', (done: any) => {
const requestInterception: Observable<any> = interceptor.intercept(executionContext, nextCallHander);
requestInterception.subscribe({
next: value => {
// ... ??? ...
},
error: error => {
throw error;
},
complete: () => {
done();
},
});
});
});
});
I currently don't know what to pass into the next callback but when I try to run the test as it is it says that the requestInterception variable is undefined. So the test fails before reaching the next callback. So the error message I get is
TypeError: Cannot read property 'subscribe' of undefined
I also tried to test the response interceptor and came up with this
const executionContext: any = {
switchToHttp: jest.fn().mockReturnThis(),
getResponse: jest.fn().mockReturnThis()
};
const nextCallHander: CallHandler<any> = {
handle: jest.fn()
};
describe("ResponseInterceptor", () => {
let interceptor: ResponseInterceptor;
beforeEach(() => {
interceptor = new ResponseInterceptor();
});
describe("intercept", () => {
it("should fetch the statuscode and response data", (done: any) => {
const responseInterception: Observable<any> = interceptor.intercept(
executionContext,
nextCallHander
);
responseInterception.subscribe({
next: value => {
// ...
},
error: error => {
throw error;
},
complete: () => {
done();
}
});
});
});
});
This time I get an error at the interceptor
TypeError: Cannot read property 'pipe' of undefined
Would some mind helping me to test those two interceptors properly?
Thanks in advance
Testing interceptors can be one of the most challenging parts of testing a NestJS application because of the ExecutionContext and returning the correct value from next.
Let's start with the ExecutionContext:
You've got an all right set up with your current context, the important thing is that you have a switchToHttp() method if you are using HTTP (like you are) and that whatever is returned by switchToHttp() has a getResponse() or getRequest() method (or both if both are used). From there, the getRequest() or getResponse() methods should return values that are used from the req and res, such as res.statusCode or req.originalUrl. I like having incoming and outgoing on the same interceptor, so often my context objects will look something like this:
const context = {
switchToHttp: jest.fn(() => ({
getRequest: () => ({
originalUrl: '/',
method: 'GET',
params: undefined,
query: undefined,
body: undefined,
}),
getResponse: () => ({
statusCode: 200,
}),
})),
// method I needed recently so I figured I'd add it in
getType: jest.fn(() => 'http')
}
This just keeps the context light and easy to work with. Of course you can always replace the values with more complex ones as you need for logging purposes.
Now for the fun part, the CallHandler object. The CallHandler has a handle() function that returns an observable. At the very least, this means that your next object needs to look something like this:
const next = {
handle: () => of()
}
But that's pretty basic and doesn't help much with logging responses or working with response mapping. To make the handler function more robust we can always do something like
const next = {
handle: jest.fn(() => of(myDataObject)),
}
Now if needed you can override the function via Jest, but in general this is enough. Now your next.handle() will return an Observable and will be pipable via RxJS operators.
Now for testing the Observable, you're just about right with the subscribe you're working with, which is great! One of the tests can look like this:
describe('ResponseInterceptor', () => {
let interceptor: ResponseInterceptor;
let loggerSpy = jest.spyOn(Logger.prototype, 'debug');
beforeEach(() => {
interceptor = new ResponseInterceptor();
});
afterEach(() => {
loggerSpy.resetMock();
});
describe('intercept', () => {
it('should fetch the request object', (done: any) => {
const responseInterceptor: Observable<any> = interceptor.intercept(executionContext, nextCallHander);
responseInterceptor.subscribe({
next: value => {
// expect the logger to have two parameters, the data, and the intercept function name
expect(loggerSpy).toBeCalledWith({statusCode: 200, responseData: value}, 'intercept');
},
error: error => {
throw error;
},
complete: () => {
// only logging one request
expect(loggerSpy).toBeCalledTimes(1);
done();
},
});
});
});
});
Where executionContext and callHandler are from the values we set up above.
A similar idea could be done with the RequestInterceptor, but only logging in the complete portion of the observer (the subscribe callback) as there are no data points returned inherently (though it would still work either way due to how observables work).
If you would like to see a real-world example (albeit one with a mock creation library), you can check out my code for a logging package I'm working on.
I am trying to create this api.ts file and put the redis client on the request to pass it to all the routes. But I am getting an error that there is no client on the Request>. How do I extend the request type and put it on the request in TypeScript?
Here is my file
import { version } from '../../package.json';
import { Router, Request, Response, NextFunction } from 'express';
export default ({ config, client } : any) => {
let api = Router();
api.use((req: Request, res: Response, next: NextFunction) => {
req.client = client;
next()
})
api.get('/', (_req, res) => {
res.json({ version });
});
return api;
}
Try this,
req['client'] = client;