I am working on a NodeJS app and I am having trouble understanding & mocking external API calls. Here is my code:
contactController.ts
import { APIService } from '../client/ExternalRestClient';
const url = process.env.API_URL;
const apiKey = process.env.API_KEY;
const apiSecret = process.env.API_SECRET;
const client = new APIService(url, apiKey, apiSecret);
export default class contactController{
public async getContact(id) {
const response = await client.getContactById(this.contactID);
return response;
}
}
ExternalRestClient.ts
import { RestClient } from "./RestClient";
export default class APIService extends RestClient {
private apiKey: string;
private apiSecret: string;
public constructor(
url: string,
_apiKey: string,
_apiSecret: string
) {
super(url);
this.apiKey = _apiKey;
this.apiSecret = _Secret;
}
public async getContactById(id) {
const data = await this.axiosClient.get(
`${this.url}/${id}`,
{
headers: {
client_id: this.apiKey,
client_secret: this.apiSecret,
},
}
);
return data;
}
}
RestClient.ts
import axios, { AxiosInstance, AxiosResponse } from "axios";
declare module "axios" {
interface AxiosResponse<T = any> extends Promise<T> {}
}
export abstract class RestClient {
protected readonly axiosClient: AxiosInstance;
protected readonly url: string;
constructor(url: string) {
this.url = url;
this.axiosClient = axios.create({
url,
});
this._initializeResponseInterceptor();
}
private _handleResponse = ({ data }: AxiosResponse) => data;
protected _handleError = (error: any) => Promise.reject(error);
private _initializeResponseInterceptor = () => {
this.axiosClient.interceptors.response.use(
this._handleResponse,
this._handleError
);
};
}
I am trying to write a test for contactController. I tried using jest.mock('axios') but it didn't work out. This is how I was doing it:
import contactController from "../src/controllers/contactController"
import axios from "axios";
jest.mock("axios");
describe("Test", () => {
describe("Individual ID", () => {
it("Checking information retrived", async () => {
const controller = new contactController();
const expected = {
"dataResponse": "success",
"id": "1234",
"hasMore": false
};
axios.get.mockResolvedValue(expected);
return controller.getContact("1234").then(data => expect(data).toEqual(expected));
});
});
});
Can someone please advice how can I write the test for this contoller? I am not able to grasp or figure out how should I proceed.
Thakns.
You are mocking the axios.get method, while your code is using axios.create(...).get basically.
Perhaps there is a better solution, but I would try mocking .create like this:
axios.create.mockResolvedValue({ get: () => expected })
That should mock the create method to return an instance that returns expected result upon calling .get on it.
P.S.: I didn't try it myself, so perhaps you also have to add proper methods to the mock to make sure that this snippet doesn't fail with trying to access some method on undefined:
private _initializeResponseInterceptor = () => {
this.axiosClient.interceptors.response.use(
this._handleResponse,
this._handleError
);
};
Related
Passing a middleware to authenticate user before accessing this route.
When I'm passing tokenController.authUser as a middleware tokenService inside tokenController is undefined. However when I run this method as a function inside the route instead of a middleware it works fine.
server.post('/api/admin/test', { preHandler: [tokenController.authUser] }, async (request: any, reply: any) => {
return null
});
Token Controller :-
import { Users } from "#prisma/client";
import ITokenService from "../../services/tokenService/ITokenService";
import ITokenController from "./ITokenController";
export default class TokenController implements ITokenController {
private readonly tokenService: ITokenService;
constructor(_tokenService: ITokenService) {
this.tokenService = _tokenService;
}
async authUser(request: any, reply: any): Promise<Users | Error> {
const authHeader = request.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (token === null)
return reply.code(401);
try {
const result = await this.tokenService.verifyToken(token);
console.log(result);
return result;
}
catch (e) {
reply.code(401);
return new Error("Error");
}
}
}
Token Service :-
import { Users } from "#prisma/client";
import ITokenService from "./ITokenService";
export default class TokenService implements ITokenService {
private readonly sign: Function;
private readonly verify: Function;
private readonly secretKey: string;
constructor(sign: Function, verify: Function, _secretKey: string) {
this.sign = sign;
this.verify = verify;
this.secretKey = _secretKey;
}
public async generateToken(user: Users): Promise<string> {
return await this.sign({ user }, this.secretKey);
}
public async verifyToken(token: string): Promise<Users | Error> {
const result = await this.verify(token, this.secretKey);
return result;
}
}
For some reason making a separate middleware function and calling tokenController.authUser inside that method works fine.
const middleware = (_req, _res, next) => {
console.log('middleware');
next()
}
server.post('/api/admin/test', { preHandler: [middleware] }, async (request: any, reply: any) => {
return null
});
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 am creating a project with Node JS and Typescript, I have a class and an interface where I type all the data. What I need to know is how to apply it to the classroom.
This is my interface:
export interface IController {
url: string,
api: string
}
This is my class where I want to apply it:
import { Request, Response } from 'express';
import { constUtils } from '../utils/const.utils';
import { IController } from '../utils/model.utils';
import { utilsController } from '../utils/index.utils';
class IndexController {
public async index(req: Request, res: Response): Promise<IController> {
try {
let api = req.query.api;
let constUt = new constUtils();
let url = constUt.conf.API_MOCS[`${api}`].url;
await utilsController.utilsCsvConverter(api, url);
} catch (error) {
console.log(error);
}
}
}
export const indexController = new IndexController();
This assumes that the local variables url and api should be returned in a Promise resolving to an object specified the by IController interface:
import { Request, Response } from 'express';
import { constUtils } from '../utils/const.utils';
import { IController } from '../utils/model.utils';
import { utilsController } from '../utils/index.utils';
class IndexController {
public async index(req: Request, res: Response): Promise<IController> {
try {
let api = req.query.api;
let constUt = new constUtils();
let url = constUt.conf.API_MOCS[`${api}`].url;
await utilsController.utilsCsvConverter(api, url);
return {url, api};
} catch (error) {
console.log(error);
}
}
}
export const indexController = new IndexController();
If you want use or apply interface to the class you should have to implement it within the applied class. Below code might give you clear idea.
class IndexController implements csvModel {
url: string;
api: string;
public async index(req: Request, res: Response): Promise<IController> {
try {
this.api = req.query.api;
let constUt = new constUtils();
this.url = constUt.conf.API_MOCS[`${api}`].url;
} catch (error) {
console.log(error);
}
}
}
export const indexController = new IndexController();
I have the following test
import proxyquire from "proxyquire";
const proxy = proxyquire.noCallThru();
const keytarStub: any = {
setPassword: () => {
console.log("MOCKED");
},
};
const foo = proxy("./keytar.service.ts", {
keytar: keytarStub,
});
describe("", () => {
let service: any;
let credentialsManagerServiceName: string = "accountdemo";
beforeAll(() => {
service = new foo.KeytarService(
credentialsManagerServiceName,
);
});
it("should", async () => {
await service.save("DemoAcc", "eee");
expect(true).toBeTruthy();
});
});
and the class I want to mock
import keytar from "keytar";
export class KeytarService {
private service: string;
constructor(
service: string,
) {
this.service = service;
}
async save(account: string, password: string): Promise<void> {
console.log(keytar);
await keytar.setPassword(this.service, account, password);
}
}
But the dependency does not get replaced.
I also included noCallThru()
Is it possible for proxyquire not to work well with TypeScript?
I'm trying to implement a passport strategy (passport-headerapikey), I was able to make it work and I can secure my routes.
But the request is empty and cannot access the logged in user ?
import { HeaderAPIKeyStrategy } from "passport-headerapikey";
import { PassportStrategy } from "#nestjs/passport";
import { Injectable, NotFoundException } from "#nestjs/common";
import { CompanyService } from "../../companies/companies.service";
#Injectable()
export class ApiKeyStrategy extends PassportStrategy(HeaderAPIKeyStrategy, "api-key") {
constructor(private readonly companyService: CompanyService) {
super(
{
header: "Authorization",
prefix: "Api-Key "
},
true,
async (apiKey, done) => {
return this.validate(apiKey, done);
}
);
}
public async validate(apiKey: string, done: (error: Error, data) => {}) {
const company = await this.companyService.findByApiKey(apiKey);
if (company === null) {
throw new NotFoundException("Company not found");
}
return company;
}
}
#UseGuards(AuthGuard("api-key"))
export class CompaniesController {
constructor(private companyService: CompanyService) {}
#Get()
#ApiOperation({ title: "Get company information" })
public getCompany(#Request() req) {
// here request is empty, so i cannot access the user..
console.log("request", req);
return [];
}
}
Thanks for your help !
To access the logged user, you can inject the object in the request. To do that, in your ApiKeyStrategy constructor, change the third parameter to something like this:
async (apiKey, verified, req) => {
const user = await this.findUser(apiKey);
// inject the user in the request
req.user = user || null;
return verified(null, user || false);
}
Now, you can access the logged user:
getCompany(#Request() req) {
console.log(req.user);
}
I hope that could help you.
As show in the documentation you should do some works to get the current user : here the documetation
First of all in the app.module make sure that the context is set :
context: ({ req }) => ({ req })
Then you can add this in the controller/resolver, this example use the Gql (GraphQL):
export const CurrentUser = createParamDecorator(
(data: unknown, context: ExecutionContext) => {
const ctx = GqlExecutionContext.create(context);
return ctx.getContext().req.user;
},
);
if this one doesnt work for you try this one instead :
export const CurrentUser = createParamDecorator(
(data: unknown, context: ExecutionContext) => {
const ctx = GqlExecutionContext.create(context);
const request = ctx.getContext();
request.body = ctx.getArgs();
return request.user;
},
);
Modify your validate method like so:
public async validate(apiKey: string, done: (error: Error, data) => {}) {
const company = await this.companyService.findByApiKey(apiKey);
if (company === null) {
return done(new NotFoundException("Company not found"), null);
}
return done(null, company);
}