Unit testing in Typescript with Dependency Injection - node.js

I'm running into problems with typescript during unit testing. I try to unit test an module like the following code example:
import {Express} from 'express'
export interface BootClassInterface {
setup(): Express
}
export class BootClass implements BootClassInterface {
constructor(private express: () => Express ){
}
public test(){
const app = this.express()
app.get("/test", (req, res) => {
res.send("Hello World")
})
return app;
}
}
For testing propose I want to know if express().get() was called and if the first parameter was '/test'. Before I switched to typescript I always used the module sinonJS to spy or stub the functionality so that I could test of a certain dependency was used correctly. Now with Typescript I run into problems with the strict types that I've set in the module. By example:
import * as chai from 'chai'
import 'mocha'
import * as sinon from 'sinon'
import * as express from 'express'
import * as Boot from '../../src/Core/Boot'
const assert = chai.assert
suite('[CORE] unit test /Core/Boot.ts', () => {
let BootClass: Boot.BootClassInterface
setup(() => {
const expressApp = () => {
const app = express()
app.get = sinon.spy()
return app
}
const Services: Boot.BootServicesInterface = { express: expressApp }
BootClass = new Boot.BootClass(Services)
})
test('Do first test', () => {
const app = BootClass.setup()
chai.assert.equal(app.get.called, 1)
})
})
The code example above results in the following Typescript compiling error:
error TS2339: Property 'called' does not exist on type 'ApplicationRequestHandler<Express>'.
I can see why typescript returns this error and somehow it is even obvious. I even know a possible solution where I accept Express as any in the module.
But I'm looking for a more elegant way to be able to spy/stub/mock my dependencies for testing propose, but at the same time have the advantages of strict typing.

AS you have stated you specify Express as the return type on BootClassInterface interface. Therefore app would be considered Express and it will look up its properties instead of your mock.
You can also just cast one property of app as any. Try:
chai.assert.equal((<any>app.get).called, 1)
Or using Sinon type definition:
chai.assert.equal((<SinonSpy>app.get).called, 1)

Related

How can I use fastify request logger in other classes without having to pass it as a parameter?

I'm new in nodejs, I'm using fastify and I want to be able to use the req.logger in all the classes functions of the flow, this because I have a the request-id on req.logger, the first solution that came to my mind is to pass as a parameter the logger through all the function/classes but I think that would make the code kind of dirty, here is an example of my code:
app.ts
import pino from 'pino';
import fastify from 'fastify';
declare module 'fastify' {
interface FastifyInstance {
// augment fastify instance with the config object types
config: Config;
}
}
function build() {
const app = fastify({
logger: pino({
name: process.env.NAME,
level: process.env.LOG_LEVEL,
}),
disableRequestLogging: true,
requestIdHeader: 'correlation-id',
requestIdLogLabel: 'correlationId',
});
// register plugins
app.register(apiRoutes, fastify => ({
getObjectUseCase: new GetObjectUseCase(
new TestClass()),
}));
return app;
}
export { build };
routes.ts
import { FastifyPluginCallback } from 'fastify';
import { StatusCodes } from 'http-status-codes';
export const apiRoutes: FastifyPluginCallback<RoutesOpts> = async (fastify, options, done) => {
const getObjectUseCase = options.getObjectUseCase;
fastify.get<object>('/v1/api/:id', async (req, reply) => {
const id = req.params.payoutId;
req.logger.info('This is a logger print'); // has the correlation id inside it while printing
const storedObject = await getObjectCase.execute(id);
reply.code(StatusCodes.OK).send(storedObject);
});
}
GetObjectUseCase.ts
export class GetObjectUseCase {
private anotherClass: TestClass;
constructor(anotherClass: TestClass) {
this. anotherClass = anotherClass;
}
async execute(id: string): Promise<StoredObject> {
// I want to use the logger here with have the correlation id on it without having to pass it as an argument on the method, how is it posible?
return this.anotherClass.getById(id);
// also needed to use it inside anotherClass.getById so I will need to pass the logger also in the method
}
}
Hope I have been clear.
Thanks!
This may not be the best or only way to do it, but this has worked for me in the past.
Typically I structure my projects with an app.ts that just instantiates my FastifyInstance and then exports the log from that created instance. This allows me to use the log where ever I want to.
It looks something like this.
app.ts
import fastify from 'fastify';
const app = fastify({ logger: true /* Your logging configuration */});
export default app;
export const logger = app.log; // Allows me to log where ever I want.
server.ts
import app from './app';
... // All your fastify configuration and other stuff.
app.listen({ ... });
Now I can use the logger outside of fastify stuff.
get-object-use-case.ts
import { logger } from './app'; // Import your fastify logger to use in this class.
export class GetObjectUseCase {
private anotherClass: TestClass;
constructor(anotherClass: TestClass) {
this. anotherClass = anotherClass;
}
async execute(id: string): Promise<StoredObject> {
logger.info({/* Whatever you want to log here. */}); // Now you can use the logger here.
return this.anotherClass.getById(id); // You can just import the logger into the TestClass file to get logging enabled there.
}
}
This even allows you to log before your FastifyInstance is started. Check out this codesandbox for a running example.

how to setup morgan-boddy in nestjs

I want to setup morgan-boddy as a midldleware to log requests and responses.
So I've created a function like that:
export function RequestLogging(app) {
const logger = new Logger('Request');
app.use(
morganBody(app, {
stream: { // <--- error is here "Void function return value is used "
write: (message) => logger.log(message.replace('\n', '')),
},
}),
);
}
That I call on main.ts
// main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
useRequestLogging(app);
// ...
}
However it seems does not work. I've got an error 'Void function return value is used' on line stream: {
Any idea how to fix?
UPDATE:
I tried to go different path and actually just stick morganBody in to main.ts as per docs:
import bodyParser from 'body-parser';
import morganBody from 'morgan-body';
app.use(bodyParser.json());
// hook morganBody to express app
morganBody(app); <-- getting error here "TS2345: Argument of type 'INestApplication' is not assignable to parameter of type 'Application'."
I wish there was a proper documentation how to approach in nestjs.
This is a pretty interesting middleware. It ends up needing the express instance itself because it calls app.use(morgan) and app.response.send() under the hood. I would really look into some other solution instead of something that accesses the response in this way.
Either way: this set up works
import { Logger } from '#nestjs/common';
import { NestFactory } from '#nestjs/core';
import * as morgan from 'morgan-body';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const logger = app.get(Logger);
(morgan as any)(app.getHttpAdapter().getInstance(), {
stream: {
write: (message: string) => {
logger.log(message.replace('\n', ''));
return true;
},
},
});
await app.listen(3033);
}
bootstrap();
The types for the package are wrong as well, it's not a default export, but a named one, so import morgan from 'morgan-body' doesn't work as advertised (at least it didn't for me).
The return true is necessary because write expects a stream.writable() method, which has returns a boolean. You can just default this to true. Then you have to use app.getHttpAdapter().getInstance() so that you ensure you pass the express instance to the middleware. Again, wonky setup, but it works.

How to inject dependencies into Firebase/Google Cloud Functions? (unit & integration testing)

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:

How to test a module in NestJs

I'm adding tests on a project and improving coverage. I would like to know how can I test a module definition (mymodule.module.ts file) in NestJs.
In particular, I'm testing a main module that imports other modules, one of them inits a db connection, this means that I need to mock the service on the other module, to avoid a real db connection.
At this moment I have something like this:
beforeEach(async () => {
const instance: express.Application = express();
module = await NestFactory.create(MyModule, instance);
});
describe('module bootstrap', () => {
it('should initialize entities successfully', async () => {
controller = module.get(MyController);
...
expect(controller instanceof MyController).toBeTruthy();
});
});
This works, but I'm sure this can get improvements :)
The ideal thing would be something like overrideComponent method provided by Test.createTestingModule.
P.S: I'm using 4.5.2 version
Improved my tests based on #laurent-thiebault answer:
import { Test } from '#nestjs/testing';
import { ThingsModule } from './things.module';
import { ThingsResolver } from './things.resolver';
import { ThingsService } from './things.service';
describe('ThingsModule', () => {
it('should compile the module', async () => {
const module = await Test.createTestingModule({
imports: [ThingsModule],
}).compile();
expect(module).toBeDefined();
expect(module.get(ThingsResolver)).toBeInstanceOf(ThingsResolver);
expect(module.get(ThingsService)).toBeInstanceOf(ThingsService);
});
});
You can now test your module (at least for code coverage) by building it that way:
describe('MyController', () => {
let myController: MyController;
beforeEach(async () => {
const module = await Test.createTestingModule({
imports: [MyModule],
}).compile();
myController = module.get<MyController>(MyController);
});
it('should the correct value', () => {
expect(myController.<...>).toEqual(<...>);
});
});
We usually didn't test .module.ts files directly.
we do this in e2e testing.
but I wonder why one should test the the module ! and you are trying to test if the module can initialize it's components , it should.
but i recommend you do this in e2e testing.
in my opinion, in unit testing you should focus on testing the services or other components behaviors not the modules.

TypeScript and Express, Testing and exporting a router module

I'm getting my head around Node, Express and TypeScript, in doing so I'm looking to build up some tests.
To follow best practices, SOLID etc I've separated my routers from my controllers.
I have an index.ts file
import { Router } from 'express';
import { TestController } from '../controllers/test.controller';
let testController = new TestController();
let index = Router();
/* GET home page. */
index.get('/', (req, res, next) => testController.get(req, res));
export default index;
I also have an index.spec.ts file
import assert = require('assert');
import * as mocha from 'mocha';
import { index } from '../routes/index';
import { TestController } from '../controllers/test.controller';
import { Request, Response } from 'express';
var sinon = require('sinon');
var should = require('should');
import app from '../App';
describe('Indexrouter', function () {
describe('#Calling get /', function () {
it('should call test controller get function.', function () {
});
});
});
When I try and run the tests via gulp i get the following messages
"D:/Dev/Learning/node/ts-express/src/routes/index.ts(4,7): Exported variable 'index' has or is using name 'Router' from external module \"express-serve-static-core\" but cannot be named."
"D:/Dev/Learning/node/ts-express/src/routes/index.spec.ts(4,10): Module '\"D:/Dev/Learning/node/ts-express/src/routes/index\"' has no exported member 'index'."
"D:/Dev/Learning/node/ts-express/src/app.ts(24,14): Cannot find name 'index'."
I can run the server ok and it will produce the correct endpoint with response. Although I do get the working about exported variable 'index'. It just seems that in the tests I can't import the index module for some reason.
How can I fix the module so I don't get the exported variable error.
How can I actually test that the index route calls the get method on the controller.
I have managed to get the controller tests working and the project as a whole can be seen here

Resources