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. :)
Related
I have a Nodejs, Express server that uses Mongodb as the Database. I am trying to write some tests but I cannot get it configured properly. I need to create a mongoose connection for each block ( .test.ts file) once and then clean up the db for the other tests. Depending on how I approach this I get two different behaviours. But first my setup.
user.controller.test.ts
import { suite, test, expect } from "../utils/index";
import expressLoader from "#/loaders/express";
import { Logger } from "winston";
import express from "express";
import request from "supertest";
import { UserAccountModel } from "#/models/UserAccount";
import setup from "../setup";
setup();
import { app } from "#/server";
let loggerMock: Logger;
describe("POST /api/user/account/", function () {
it("it should have status code 200 and create an user account", async function () {
//GIVEN
const userCreateRequest = {
email: "test#gmail.com",
userId: "testUserId",
};
//WHEN
await request(app)
.post("/api/user/account/")
.send(userCreateRequest)
.expect(200);
//SHOULD
const cnt: number = await UserAccountModel.count();
expect(cnt).to.equal(1);
});
});
And my other test post.repository.test.ts
import { suite, test, expect } from "../../utils/index";
import { PostModel } from "#/models/Posts/Post";
import PostRepository from "#/repositories/posts.repository";
import { PostType } from "#/interfaces/Posts";
import { Logger } from "winston";
#suite
class PostRepositoryTests {
private loggerMock: Logger;
private SUT: PostRepository = new PostRepository({});
#test async "Should create two posts"() {
//GIVEN
const given = {
id: "jobId123",
};
//WHEN
await this.SUT.CreatePost(given);
//SHOULD
const cnt: number = await PostModel.count();
expect(cnt).to.equal(1);
}
}
And my setup
setup.ts
import { MongoMemoryServer } from "mongodb-memory-server";
import mongoose from "mongoose";
export = () => {
let mongoServer: MongoMemoryServer;
before(function () {
console.log("Before");
return MongoMemoryServer.create().then(function (mServer) {
mongoServer = mServer;
const mongoUri = mongoServer.getUri();
return mongoose.connect(mongoUri);
});
});
after(function () {
console.log("After");
return mongoose.disconnect().then(function () {
return mongoServer.stop(true);
});
});
};
With the above setup I get
1) "before all" hook in "{root}":
MongooseError: Can't call `openUri()` on an active connection with different connection strings. Make sure you aren't calling `mongoose.connect()` multiple times. See: https://mongoosejs.com/docs/connections.html#multiple_connections
But if I don't import the setup.ts and I rename it to setup.test.ts, then it works but the data isn't cleared after each run so I actually have 10 new users created instead of 1. Every time I run it, it works and doesn't clear the data after it's finished.
Also I have a big issue where the tests hang and don't seem to finish. I am guessing that is because of the async, await in the tests or because of the hooks hanging.
What I want to happen is:
Each test should have it's own setup, the mongo memory server should be clean every time.
The tests should use async and await and not hang.
Somehow export the setup from 1) as a utility function so that I can reuse it in my code
I don't know whether my question is really related to Firebase Cloud Functions, but I came across this problem trying to test my Firebase Cloud Functions.
Let's say I have a Firebase Cloud function written in NodeJS:
function.ts
import * as functions from "firebase-functions"
const admin = require("firebase-admin")
import * as authVerifier from "../../auth/authVerifier"
export default functions.https.onRequest(async (req, res) => {
let authId
try {
authId = await authVerifier.identifyClientRequest(req)
} catch (err) {
console.error(`Unauthorized request error: ${err}`)
return res.status(401).send({
error: "Unauthorized request"
})
}
}
Usually I have an interface and can easily mock any class I want to test it.
And, for example, authVerifier looks like:
authVerifier.ts
import * as express from "express"
export async function identifyClientRequest(req: express.Request) {
return true // whatever, it doesn't really matter, should be real logic here
}
I'm trying to test function.ts and I only can pass res and req into it, e.g:
function.test.ts
it("should verify client identity", async () => {
const req = {
method: "PUT"
}
const res = { }
await myFunctions(req as express.Request, res as express.Response)
// assert that authVerifier.identifyClientRequest(req) called with passed req
})
So the question is: how can I mock authVerifier.identifyClientRequest(req) to use different implementations in function.ts and in function.test.ts?
I don't really know NodeJS/TypeScript, so I wonder if I can import another mock class of authVerifier for test or something like that.
Ok, seems like I found the answer. I'll post it here just in case.
Using sinonjs, chai we can stub our class (authVerifier in that case) to return necessary results:
const chai = require("chai")
const assert = chai.assert
const sinon = require("sinon")
import * as authVerifier from "../../../src/auth/authVerifier"
it("should verify client identity", async () => {
const req = {
method: "PUT"
}
const res = mocks.createMockResponse()
const identifyClientRequestStub = sinon.stub(authVerifier, "identifyClientRequest");
const authVerifierStub = identifyClientRequestStub.resolves("UID")
await updateUser(req as express.Request, res as express.Response)
assert.isTrue(authVerifierStub.calledWith(req))
})
And the result is:
I have a controller and a request file that look like this, making the requests with axios(to an external API), and sending the controller response to somewhere else, my question is, how to apply Unit Testing to my controller function (getInfoById), how do I mock the axiosRequest since it's inside the controller?. I am using Jest and only Jest for testing(might need something else, but I'm not changing)
file: axiosFile.js
import axios from "axios"
export const axiosRequest = async (name) => {
const { data } = await axios.get("url")
return data
}
file: controllerFile.js
import { axiosRequest } from "./axiosFile"
export const getInfoById = async (name) => {
try {
const response = await axiosRequest(name)
return { status: 200, ...response }
} catch {
return { status: 500, { err: "Internal ServerError" } }
}
}
Thanks in advance.
PS: It's a Backend in NodeJs
You can mock the http calls using nock
This way you will be directly able to test your method by mocking the underlying http call. So in your case something like
const nock = require('nock')
const scope = nock(url)
.get('/somepath')
.reply(200, {
data: {
key: 'value'
},
})
Controller and method for testing:
import { Controller, Get, Response, HttpStatus, Param, Body, Post, Request, Patch, Delete, Res } from '#nestjs/common';
#Controller('api/parts')
export class PartController {
constructor(private readonly partsService: partsService) { }
#Get()
public async getParts(#Response() res: any) {
const parts = await this.partsService.findAll();
return res.status(HttpStatus.OK).json(parts);
}
}
And this is unit test which must test getParts method:
describe('PartsController', () => {
let partsController: PartsController;
let partsService: partsService;
beforeEach(async () => {
partsService = new partsService(Part);
partsController= new PartsController(partsService);
});
describe('findAll', () => {
it('should return an array of parts', async () => {
const result = [{ name: 'TestPart' }] as Part[];
jest.spyOn(partsService, 'findAll').mockImplementation(async () => result);
const response = {
json: (body?: any) => {
expect(body).toBe(result);
},
status: (code: number) => response,
};
await partsController.getParts(response);
});
});
});
This test works correctly, but I think this is a bad solution. When I investigated this problem, I saw this option:
const response = {
json: (body?: any) => {},
status: (code: number) => response,
};
expect(await partsController.getParts(response)).toBe(result);
But when I try it my test don't work, cause await partsController.getParts(response) // undefined
So what should I do to make my test look good?
In solution I use: nodeJS sequelize, nestJS, typescript
Alright so I guess your problems lies on the way you instantiate and use your controller & service.
Let NestJs Testing utils do the job for you, like this:
describe('Parts Controller', () => {
let partsController: PartsController;
let partsService: PartsService;
beforeEach(async () => {
// magic happens with the following line
const module = await Test.createTestingModule({
controllers: [
PartsController
],
providers: [
PartsService
//... any other needed import goes here
]
}).compile();
partsService = module.get<PartsService>(PartsService);
partsController = module.get<PartsController>(PartsController);
});
// The next 4 lines are optional and depends on whether you would need to perform these cleanings of the mocks or not after each tests within this describe section
afterEach(() => {
jest.restoreAllMocks();
jest.resetAllMocks();
});
it('should be defined', () => {
expect(partsController).toBeDefined();
expect(partsService).toBeDefined();
});
describe('findAll', () => {
it('should return an array of parts', async () => {
const result: Part[] = [{ name: 'TestPart' }];
jest.spyOn(partsService, 'findAll').mockImplementation(async (): Promise<Part[]> => Promise.resolve(result));
const response = {
json: (body?: any) => {},
status: (code: number) => HttpStatus.OK,
};
expect(await partsController.getParts(response)).toBe(result);
});
});
});
I haven't tested the code myself so give it a try (not too sure about the response mocking for the Response type in the Parts Controller tho).
Regarding the Parts Controller by the way you should take advantage of the Response type from express though - try to rewrite code as follows:
import { Controller, Get, Response, HttpStatus, Param, Body, Post, Request, Patch, Delete, Res } from '#nestjs/common';
import { Response } from 'express';
#Controller('api/parts')
export class PartController {
constructor(private readonly partsService: partsService) { }
#Get()
public async getParts(#Response() res: Response) { // <= see Response type from express being used here
const parts = await this.partsService.findAll();
return res.status(HttpStatus.OK).json(parts);
}
}
Finally have a look at this section of the nest official documentation, maybe it can give you some insights about what you're trying to achieve:
- Nest testing section
- Nest library approach
In the second link, at the almost beginning of the page, it is stated in the https://docs.nestjs.com/controllers#request-object section the following:
For compatibility with typings across underlying HTTP platforms (e.g., Express and Fastify), Nest provides #Res() and #Response()
decorators. #Res() is simply an alias for #Response(). Both directly
expose the underlying native platform response object interface. When
using them, you should also import the typings for the underlying
library (e.g., #types/express) to take full advantage. Note that when
you inject either #Res() or #Response() in a method handler, you put
Nest into Library-specific mode for that handler, and you become
responsible for managing the response. When doing so, you must issue
some kind of response by making a call on the response object (e.g.,
res.json(...) or res.send(...)), or the HTTP server will hang.
Hope it helps, don't hesitate to comment, or share your solution if it helped finding another one ! :)
Welcome to StackOverflow platform by the way !
I would like to test getFund() method from my service. I use NestJS that uses jest by default.
I have no idea how to test this line with jest: return await this.fundModel.findById(id);. Any idea?
import { Injectable } from '#nestjs/common';
import { Model } from 'mongoose';
import { Fund } from '../../funds/interfaces/fund.interface';
import { InjectModel } from '#nestjs/mongoose';
#Injectable()
export class FundService {
constructor(
#InjectModel('Fund')
private readonly fundModel: Model<Fund>,
) {}
/*****
SOME MORE CODE
****/
async getFund(id: string): Promise<Fund> {
return await this.fundModel.findById(id);
}
}
Edit
Thanks to slideshowp2 answer, I wrote this test.
describe('#getFund', () => {
it('should return a Promise of Fund', async () => {
let spy = jest.spyOn(service, 'getFund').mockImplementation(async () => {
return await Promise.resolve(FundMock as Fund);
});
service.getFund('');
expect(service.getFund).toHaveBeenCalled();
expect(await service.getFund('')).toEqual(FundMock);
spy.mockRestore();
});
});
The problem is that I get this result in my coverage report:
When I hover the line I get statement not covered.
There is only one statement return await this.fundModel.findById(id); in your getFund method. There is no other code logic which means the unit test you can do is only mock this.fundModel.findById(id) method and test
it .toBeCalledWith(someId).
We should mock each method and test the code logic in your getFund method. For now, there is no other code logic.
For example
async getFund(id: string): Promise<Fund> {
// we should mock this, because we should make an isolate environment for testing `getFund`
const fundModel = await this.fundModel.findById(id);
// Below branch we should test based on your mock value: fundModel
if(fundModel) {
return true
}
return false
}
Update
For example:
describe('#findById', () => {
it('should find ad subscription by id correctly', async () => {
(mockOpts.adSubscriptionDataSource.findById as jestMock).mockResolvedValueOnce({ adSubscriptionId: 1 });
const actualValue = await adSubscriptionService.findById(1);
expect(actualValue).toEqual({ adSubscriptionId: 1 });
expect(mockOpts.adSubscriptionDataSource.findById).toBeCalledWith(1);
});
});
The test coverage report: