How to unit test a chain of express middlewares? - node.js

I have a controller class that exports an array containing a number of middlewares (utilizing express-validator) that verify that all essential parts of a request are present and as the last element of the array the handler for the request:
Controller class:
import { body, ValidationChain } from "express-validator/check";
import { Request, Response, Handler } from "express";
export default class UserController {
signupChainPost: Handler[];
constructor() {
this.signupChainPost = [
this.getEmailExistsMiddleware("email"),
this.getPasswordValidationMiddleware("password"),
this.signupHandlerPost.bind(this)
];
}
private getEmailExistsMiddleware(fieldName: string): ValidationChain {
return body(fieldName, "some error").isEmail().custom((value, { req }) => {
// ...
});
}
private getPasswordValidationMiddleware(fieldName: string): ValidationChain {
return body(fieldName, "some error").isLength({ min: 5 });
}
private async signupHandlerPost(req: Request, res: Response) {
// request handling
}
}
Usage of the controller class in the main server file using Epxress.js:
const userController = new UserController();
app.post("/user/create", userController.signupChainPost);
Now I want to write unit tests for my request handler but I can't figure out how to test an array of middlewares/handlers. Should I only test the "real handler" and trust the middlewares to prevent invalid requests from reaching my handler? Or is my application design fundamentally wrong which leads to the unit tests being hard to write?
Thanks in advance for the help :)

Related

How to create common class for third-party API requests in NestJS

I am creating NestJS application where I am making third-party API requests. For that I have to write the same thing inside every function in order to get the data.
To make things non-repeating, how can I write on common class that has API request based on GET or POST request and send the response so that I can use that class in every function.
Below is my code:
subscribe.service.ts
#Injectable()
export class SubscribeService {
constructor(#InjectModel('Subscribe') private readonly model:Model<Subscribe>,
#Inject(CACHE_MANAGER) private cacheManager:Cache,
private httpService: HttpService){}
async addSubscriber(subscriberDto:SubscribeDto){
const url = 'https://track.cxipl.com/api/v2/phone-tracking/subscribe';
const headersRequest = {
'content-Type': 'application/json',
'authkey': process.env.AUTHKEY
};
try{
const resp = await this.httpService.post(url,subscriberDto,{ headers: headersRequest }).pipe(
map((response) => {
if(response.data.success == true){
const data = new this.model(subscriberDto);
// return data.save();
const saved = data.save();
if(saved){
const msgSuccess = {
"success":response.data.success,
"status":response.data.data.status
}
return msgSuccess;
}
}
else{
const msgFail = {"success":response.data.success}
return msgFail;
}
}),
);
return resp;
}
catch(err){
return err;
}
}
async getLocation(phoneNumber:PhoneNumber){
try{
const location = await this.cacheManager.get<Coordinates>(phoneNumber.phoneNumber);
if(location){
return location;
}
else{
const resp = await axios.post('https://track.cxipl.com/api/v2/phone-tracking/location',phoneNumber,{headers:{
'content-Type': 'application/json',
'authkey': process.env.AUTHKEY
}});
const msg:Coordinates = {
"location":resp.data.data.location,
"timestamp":resp.data.data.timestamp
}
await this.cacheManager.set<Coordinates>(phoneNumber.phoneNumber,msg, { ttl: 3600 });
return msg;
}
}
catch(err){
console.log(err);
return err;
}
}
}
As in above code in both function addSubscriber() and getLocation() I need to hit the API repeatedly and add request headers again and again is there any way so that I can create one separate class for request and response and utilize in my service.
How can I achieve desired the result?
To create a common class for making third-party API requests in NestJS, you can follow these steps:
Create a new file in your NestJS project to store the common class.
For example, you could create a file called api.service.ts in the
src/common directory.
In the file, create a new class called ApiService that will be responsible for making the API requests. This class should have a
constructor that injects the necessary dependencies, such as the
HttpService provided by NestJS.
import { HttpService, Injectable } from '#nestjs/common';
#Injectable()
export class ApiService {
constructor(private readonly httpService: HttpService) {}
}
Add methods to the ApiService class for each type of API request you want to make. For example, you might have a get() method for making GET requests, a post() method for making POST requests, and so on. Each method should accept the necessary parameters for making the request (such as the URL and any query parameters or request body), and use the HttpService to make the request.
import { HttpService, Injectable } from '#nestjs/common';
#Injectable()
export class ApiService {
constructor(private readonly httpService: HttpService) {}
async get(url: string, params?: object): Promise<any> {
return this.httpService.get(url, { params }).toPromise();
}
async post(url: string, body: object): Promise<any> {
return this.httpService.post(url, body).toPromise();
}
}
Inject the ApiService wherever you need to make API requests. For example, you might inject it into a service or a controller, and use the methods of the ApiService to make the actual API requests.
import { Injectable } from '#nestjs/common';
import { ApiService } from './api.service';
#Injectable()
export class SomeService {
constructor(private readonly apiService: ApiService) {}
async getData(): Promise<any> {
return this.apiService.get('https://some-api.com/endpoint');
}
}
This is just one way you could create a common class for making third-party API requests in NestJS. You can customize the ApiService class to meet the specific needs of your application

Why can't I access `userService` variable after express instance running in Nodejs

I was trying to create an endpoint in node.js, more specifically in express
but I am not sure why I can't access userService variable when requesting from a client.
I've gotten Cannot read property 'userService' of undefined, but when i move ServicesFactory.getInstance().getUserService() inside the signUp function it works?!
I am guessing that node.js garbage collects it due to it's not being used until the user make a request.
export class UserApi implements WebEndpoint {
router: Router
userService = ServicesFactory.getInstance().getUserService()
constructor() {
this.router = Router()
this.router.post('/signup', this.signUp)
}
signUp(req: Request, res: Response): void {
const user: User = req.body
this.userService.signUp(user)
res.send("Successfully registered")
}
}
I found the problem, so basically I am a noob.
consider this example
class a {
constructor() {
this.a1 = 'hello';
}
greet(){
const greeting = `${this.a1} dude!`;
console.log(greeting);
};
}
class b {
b1 = new a();
constructor() {
this.b1.greet.call();
}
}
new b();
Now it wouldn't run, because b class called greet method with a new context, the same with express when you provide a function as a handler on an Express endpoint it will be called with a new set of context (read:this) that's why this.userService in my code above won't work because there is no userService property in the context provided by Express.
The solution is simple. Arrow function.
signUp = (req: Request, res: Response): void => {
const user: User = req.body
this.userService.signUp(user)
res.send("Successfully registered")
}
Now the function will inherit it's class's context.You can refer to this for more detail answer.

How can i unit test the dependency method

It's my code written in Typescript;
I want to test the private getFunc method and the method of redisClient have been called Once.
I use supertest to invoke the API, but I can't expect the redis method.
import { Request, Response, Router } from "express";
import * as redis from "redis";
const redisOption: redis.ClientOpts = {
host: "127.0.0.1",
port: 6379,
detect_buffers : true,
db: 0,
retry_strategy: () => 60000
}
const redisClient: redis.RedisClient = redis.createClient(redisOption);
export class IndexRoutes {
public router: Router;
constructor() {
this.router = Router();
this.init();
}
public init() {
this.router.get("/", this.getFunc);
}
private getFunc = async (req: Request, res: Response) => {
return res.status(200).send(await redisClient.set("test", "123"));
}
}
error: Uncaught AssertionError: expected get to have been called
exactly once, but it was called 0 times
Help me, how do I properly stub the redisClient.get(...) function?
First of all, you don't usually test dependencies/dependency methods. You only test your code.
Secondly, I think you're saying you want want to check if redis.get() is being called or not. That means you'll have to spy on it.
jest.spyOn() is something you should check out.
Your test should look something like:
import * as redis from 'redis';
describe('my redis wrapper', () => {
it('Should call get when my wrapper\'s getFunc is called', () => {
let myRedisSpy = jest.spyOn(redis.prototype, 'get');
// call your function here
expect(myRedisSpy).toHaveBeenCalledOnce();
});
});
Or something similar, I don't know if this code will work as is. But, you're always welcome to try.

Using Service in Express Router

I am pretty new in the NodeJS but I would like to learn something new. I came from .NET fancy dependency injection, inversion of controll, microservice shiny world so I am trying write some service in TypeScript based on my previous experiences.
I am using express and express router to create some api. I have some methods in router which handles api calls and I want to use some kind of service object for data retrieving and manipulation.
I inject the service into the router using constructor injection but if I want to use my service it throws an error:
TypeError: Cannot read property 'layoutService' of undefined
I understood that the methods were called withouth context so I added .bind(this) to the each method regsitration and it works, but I dont know if it is the best way how to do it.
Does anyone have a better idea?
simplified server.ts
import express, { Router } from "express";
// inversion of controll
import container from "./ioc";
import { TYPE } from "./constants";
import IMyService from "./abstract/IMyService";
// import routers
import MyRouter from "./api/MyRouter";
app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
const router: Router = express.Router();
const myRouter: MyRouter = new MyRouter(container.get<IMyService>(TYPE.IMyService));
app.use("/", router);
app.use("/api/v1/layouts", layoutRouter.router);
MyRouter.ts
import IMyService from "./abstract/IMyService";
import { Router, Request, Response } from "express";
import { inject } from "inversify";
import { TYPE } from "../constants";
export default class MyRouter {
public readonly router: Router;
private readonly myService: IMyService;
constructor(
#inject(TYPE.IMyService) myService: IMyService
) {
this.myService = myService;
this.router = Router();
this.routes();
}
public GetAll(req: Request, res: Response): void {
this.myService.getAll()
.then(data => {
const status: number = res.statusCode;
res.json({ status, data });
})
.catch(err => {
const status: number = res.statusCode;
res.json({ status, err });
});
}
public GetOne(req: Request, res: Response): void {
const id: string = req.params.id;
this.myService.getOne(new ObjectID(id))
.then(data => {
const status: number = res.statusCode;
res.json({ status, data });
})
.catch(err => {
const status: number = res.statusCode;
res.json({ status, err });
});
}
routes(): void {
this.router
.get("/", this.GetAll)
.get("/:id", this.GetOne);
}
}
If you define your function with the arrow syntax (ES6), it will "bind" the context to it automatically and you won't need to bind them. But it will depends on your use case (ou might need to bind a different context)

Unit testing only controller file instead of express framework

I am trying to unit test my controller file that handles a particular request. I do not want to test express as we have another mechanism for testing our Apis. I am stubbing the data using sinon. But the response is not what I am expecting.. I am always getting back a 200 status instead of the 201 that I am supposed to receive. Am I missing out on any specific part? I am using Typescript and mocha
my spec.ts file is
import {expect} from 'chai';
import {HiringCompanyJobHandler} from
"../../source/Handlers/HiringCompanyJobHandler";
import * as httpMocks from 'node-mocks-http';
import * as sinon from 'sinon';
import Jobs from "../../source/DAL/model/job";
import * as mongoose from 'mongoose';
import {Response, Request, Express} from 'express';
describe("Hiring Company Job Handler Unit Test Cases", () => {
let request = null;
let response = null;
beforeEach(function () {
response = httpMocks.createResponse();
});
it("Should return the job details that belong to a particular company", (done) => {
request = {
query: {
UserName: 'HiringCompany',
CompanyID: '1'
}
};
let expectedJobs = [
{
'hiringCompanyId': '1',
'companyJobId': 'T1',
'jobName': 'Plumber',
'jobType': 'Permanent'
}, {
'hiringCompanyId': '1',
'companyJobId': 'T2',
'jobName': 'Electrician',
'jobType': 'Temporary'
}
];
sinon.mock(Jobs)
.expects('find')
.resolves(expectedJobs);
let hiringCompanyJobHandler = new HiringCompanyJobHandler();
hiringCompanyJobHandler.GetBasicJobInformationForHiringCompany(request, response);
expect(response.statusCode).to.equal(201);
done();
})
});
My controller code is as follows
import { NextFunction, Request, Response } from "express";
import { isNullOrUndefined } from "util";
import JobDal from "../DAL/jobdal";
export class HiringCompanyJobHandler {
constructor() {
}
public GetBasicJobInformationForHiringCompany(req: Request, res: Response) {
let userName = req.query.UserName;
let companyID = req.query.CompanyID;
let status = req.query.status;
let jobDal = new JobDal();
jobDal.GetHiringCompanyJobDetails(companyID,status).then((details) => {
if (details.length > 0) {
res.status(201).send({ details });
}else {
res.status(200).send({ status: "No jobs found!" });
}
}).catch((error: any) => {
console.log(error);
res.status(500).send(error);
});
}
}
export default new HiringCompanyJobHandler();
resolves does not appear to be available for mocks (check the api here).
I guess that you want to modify the behavior of the method find in Job, and that this method is called inside GetHiringCompanyJobDetails. Is that correct?
I think though that you should stub the method GetHiringCompanyJobDetails instead, so your test is pure unit test, since in here you are unit testing the method GetBasicJobInformationForHiringCompany. But this would be part of a different discussion.
In any case, you should use a stub if you want to modify the behavior of a method to meet the requirements of the path you want to test.
Check the sinon API for stubs here. I hope this solves your issue. :)

Resources