Mocking a function in a jest environment file - node.js

I want to share a server between all my tests, to do this I create the file server-environment.js
const NodeEnvironment = require('jest-environment-node')
const supertest = require('supertest')
//A koa server
const { app, init } = require('../src/server')
class ServerEnvironment extends NodeEnvironment {
constructor(config, context) {
super(config, context)
this.testPath = context.testPath
}
async setup() {
await init
this.server = app.listen()
this.api = supertest(this.server)
this.global.api = this.api
}
async teardown() {
this.server.close()
}
}
module.exports = ServerEnvironment
The thing is that I want to mock some middleware that the servers routes use but I can't really find a way to do that. If I try to declare jest.mock anywhere in the file I get the error that jest isn't defined. If I mock the function in the actual test file the global wouldn't make use of it. Not sure if something like this would be possible to do with Jest?

I had a same issue and solved it by adding setupFilesAfterEnv.
jest.config.js:
module.exports = {
...
setupFilesAfterEnv: [
'./test/setupMocks.js'
]
...
};
test/setupMocks.js
jest.mock('path/to/api', () => global.api);

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.

Jest mocking module

I am writing unit tests for a node application using Jest.
The Node code is using a third party library to log information.
The library has a function getLogger which you should call to return a logger object.
I am trying to mock the calls for that library and detect its calls in my unit test.
The node code is as follows:
const logger = require('third-party-libary').getLogger('myModule')
....
function submitSomething() {
....
logger.info('log something')
}
In my Jest unit test, I tried to mock those logger calls in many different ways, with no success, and always come back as "logger is not defined"
I tried:
jest.mock('third-party-library');
const loggerFactory = require('third-party-library');
const logger = {
error: jest.fn(),
info: jest.fn()
};
loggerFactory.getLogger.mockImplementation(() => logger);
But it always return error :
cannot find "info" for null object
I tried this as well:
jest.mock('third-party-library')
const loggerFactory = require('third-party-library');
const logger = {
error: jest.fn(),
info: jest.fn()
};
loggerFactory.getLogger = () => logger
I tried this:
jest.mock('third-party-library')
const loggerFactory = require('third-party-library');
const logger = {
error: jest.fn(),
info: jest.fn()
};
loggerFactory.getLogger = jest.fn(() => logger)
With the same error
I switched between the jest.mock to make it after the require, with no luck
Your approach works fine, just note that your code creates logger as soon as it runs so the mock for getLogger has to be in place before the code is required:
jest.mock('third-party-library');
const loggerFactory = require('third-party-library');
const logger = {
error: jest.fn(),
info: jest.fn()
};
// const { submitSomething } = require('./code'); <= would NOT work here
loggerFactory.getLogger.mockReturnValue(logger);
const { submitSomething } = require('./code'); // <= works here
test('submitSomething', () => {
submitSomething();
expect(logger.info).toHaveBeenCalledWith('log something'); // Success!
});

Best practices for using sinon for nodeJS unit testing

I'm coming from Java experience with spring framework and looking for the most elegant way to write tests with mocks in nodejs.
For java its look like this:
#RunWith(SpringJUnit4ClassRunner.class)
public class AccountManagerFacadeTest {
#InjectMocks
AccountManagerFacade accountManagerFacade;
#Mock
IService service
#Test
public void test() {
//before
here you define specific mock behavior
//when
//then
}
}
Looking for something similar for nodeJS, any suggestions?
Mocking with node.js is much easier than Java thanks to javascript flexibility.
Here is a full example of class mocking, with the following class:
// lib/accountManager.js
class AccountManager {
create (name) {
this._privateCreate(name);
}
update () {
console.log('update')
}
delete () {
console.log('delete')
}
_privateCreate() {
console.log('_privateCreate')
}
}
module.exports = AccountManager
You could mock it like this:
// test/accountManager.test.js
const
sinon = require('sinon'),
should = require('should')
AccountManager = require('../lib/accountManager');
require('should-sinon'); // Required to use sinon helpers with should
describe('AccountManager', () => {
let
accountManager,
accountManagerMock;
beforeEach(() => {
accountManagerMock = {
_privateCreate: sinon.stub() // Mock only desired methods
};
accountManager = Object.assign(new AccountManager(), accountManagerMock);
});
describe('#create', () => {
it('call _privateCreate method with good arguments', () => {
accountManager.create('aschen');
should(accountManagerMock._privateCreate).be.calledOnce();
should(accountManagerMock._privateCreate).be.calledWith('aschen');
})
});
});
Here you can find more example on mocking classes and dependencies: https://github.com/Aschen/workshop-tdd/blob/master/step2/test/file.test.js

"this" lost after construct? (ts/node/express)

I am trying to build a simple http app using node-express.
Issue when setting up routes, the constructor of the MyRouter class has this but it's lost in the getRoutes() function.
class Main {
public async run(): Promise<void> {
const myRouter = new MyRouter(this.config);
// this.server is express() during construct
this.server.use("/v1", myRouter.getRoutes);
await this.server.listen(this.config.rest.port);
}
}
class MyRouter {
private router: express.Router;
constructor(private config: any) {
this.router = express.Router();
console.log(this); // Prints the whole Router object!
}
public getRoutes(): express.Router {
console.log(this); // = "undefined" !
this.router.use("/test", otherClass.getRoutes);
return this.router;
}
}
Why is this?
The value of this depends not on where it is defined but by how a function is called. You did this:
this.server.use("/v1", myRouter.getRoutes);
This is equivalent to:
var tmp = myRouter.getRoutes;
this.server.use("/v1", tmp); // `this` will refer to the global object
There are two solutions. Either wrap it in an anonymous function to retain the object that calls the function:
this.server.use("/v1", function(){return myRouter.getRoutes()});
Or use .bind()
this.server.use("/v1", myRouter.getRoutes.bind(myRouter));

Is there a trick to using Mockery in Mocha test with Typescript?

It would seem the usual method of importing in typescript prevents the modules from being mocked... Assume I have the following product code in a node.js project written in typescript that I would like to test:
// host.ts
import http = require('http');
export class Host {
public start(port: number): http.Server {
return http.createServer().listen(port);
}
}
I have the below unit test using mockery (d.ts in pull request #3313) and mocha:
import chai = require('chai');
import mockery = require('mockery');
import webserver = require('../hosting/host');
describe('host', (): void => {
describe('start()', (): void => {
before(() : void => {
mockery.enable();
});
after((): void => {
mockery.deregisterAll();
mockery.disable();
});
it('should create an http server', (): void => {
mockery.registerMock('http', {
Server: mocks.Server,
createServer: (app: any) : any => new mocks.Server(app)
});
var host: webserver.Host = new webserver.Host({ port: 111 });
var server: any = host.start();
chai.expect(server).is.instanceOf(mocks.Server);
});
});
});
module mocks {
'use strict';
export class Server {
app: any;
constructor(app: any) {
this.app = app;
}
}
}
The problem is that when import webserver = require('../hosting/host') is called the mocks in the test aren't setup yet and the un-mocked require('http') is returned. I attempted to try var http = require('http') within the Host.start function, but this prevents http.Server from being declared as a return value.
How should I go about implementing unit tests in Typescript with Mocks? Is there a better library than mockery that would work better?
After all day of scouring the web I finally learned that: Yes, there is a trick to using Mockery in Mocha test with Typescript. The trick is using the typeof identifier to reference the module. I discovered this in the Optional Module Loading and Other Advanced Loading Scenarios in this document.
My updated code now looks like this:
// host.ts
import httpDef = require('http');
export class Host {
public start(port: number): httpDef .Server {
var http: typeof httpDef = require('http');
return http.createServer().listen(port);
}
}
This allows me to set up mocks in my mocha test like this:
import chai = require('chai');
import mockery = require('mockery');
import webserver = require('../hosting/host');
import httpDef = require('http'):
describe('host', (): void => {
describe('start()', (): void => {
before(() : void => {
mockery.enable();
});
after((): void => {
mockery.deregisterAll();
mockery.disable();
});
it('should create an http server', (): void => {
var mockServer: httpDef.Server = <httpDef.Server>{};
var mockHttp: typeof httpDef = <typeof httpDef>{};
mockHttp.createServer = () : httpDef.Server => mockServer;
mockery.registerMock('http', mockHttp);
var host: webserver.Host = new webserver.Host({ port: 111 });
var server: any = host.start();
chai.expect(server).is.equals(mockServer);
});
});
});
Some other scenarios where this can be used for dependency injection can be found here.

Resources