Is there a trick to using Mockery in Mocha test with Typescript? - node.js

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.

Related

How to override url for RTK query

I'm writing pact integration tests which require to perform actual call to specific mock server during running tests.
I found that I cannot find a way to change RTK query baseUrl after initialisation of api.
it('works with rtk', async () => {
// ... setup pact expectations
const reducer = {
[rtkApi.reducerPath]: rtkApi.reducer,
};
// proxy call to configureStore()
const { store } = setupStoreAndPersistor({
enableLog: true,
rootReducer: reducer,
isProduction: false,
});
// eslint-disable-next-line #typescript-eslint/no-explicit-any
const dispatch = store.dispatch as any;
dispatch(rtkApi.endpoints.GetModules.initiate();
// sleep for 1 second
await new Promise((resolve) => setTimeout(resolve, 1000));
const data = store.getState().api;
expect(data.queries['GetModules(undefined)']).toEqual({modules: []});
});
Base api
import { createApi } from '#reduxjs/toolkit/query/react';
import { graphqlRequestBaseQuery } from '#rtk-query/graphql-request-base-query';
import { GraphQLClient } from 'graphql-request';
export const client = new GraphQLClient('http://localhost:12355/graphql');
export const api = createApi({
baseQuery: graphqlRequestBaseQuery({ client }),
endpoints: () => ({}),
});
query is very basic
query GetModules {
modules {
name
}
}
I tried digging into customizing baseQuery but were not able to get it working.

can't get the Jest provided ESM example to run

I'm just trying to get the ES6 Class Mocks example provided by Jest to run green.
here's my code repo
it's taken me way to long to even get to this point, but the tests still fail with
TypeError: SoundPlayer.mockClear is not a function
system under test
import SoundPlayer from './sound-player';
export default class SoundPlayerConsumer {
constructor() {
this.soundPlayer = new SoundPlayer();
}
playSomethingCool() {
const coolSoundFileName = 'song.mp3';
this.soundPlayer.playSoundFile(coolSoundFileName);
}
}
the test
import {jest} from '#jest/globals';
import SoundPlayer from './sound-player';
import SoundPlayerConsumer from './sound-player-consumer';
const mockPlaySoundFile = jest.fn();
jest.mock('./sound-player', () => {
return jest.fn().mockImplementation(() => {
return {playSoundFile: mockPlaySoundFile};
});
});
beforeEach(() => {
SoundPlayer.mockClear();
mockPlaySoundFile.mockClear();
});
it('The consumer should be able to call new() on SoundPlayer', () => {
const soundPlayerConsumer = new SoundPlayerConsumer();
// Ensure constructor created the object:
expect(soundPlayerConsumer).toBeTruthy();
});
it('We can check if the consumer called the class constructor', () => {
const soundPlayerConsumer = new SoundPlayerConsumer();
expect(SoundPlayer).toHaveBeenCalledTimes(1);
});
it('We can check if the consumer called a method on the class instance', () => {
const soundPlayerConsumer = new SoundPlayerConsumer();
const coolSoundFileName = 'song.mp3';
soundPlayerConsumer.playSomethingCool();
expect(mockPlaySoundFile.mock.calls[0][0]).toEqual(coolSoundFileName);
});
system under test dependency
export default class SoundPlayer {
constructor() {
this.foo = 'bar';
}
playSoundFile(fileName) {
console.log('Playing sound file ' + fileName);
}
}

Mocking a function in a jest environment file

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);

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.

Sharing socket.io between modules in typescript

I have read several examples of different ways to share a socket.io connection among modules, but I can't quite seem to put all the pieces together to architect this.
I have a SocketConnection class which I want to share among all of my code.
import * as sio from 'socket.io';
export class SocketConnection {
private static instance: SocketConnection;
server: any;
private constructor(server) {
this.server = server;
}
static getInstance(server) {
if (!SocketConnection.instance) {
SocketConnection.instance = new SocketConnection(server);
const io = sio(server);
io.on('connection', (socket) => {
console.log('conncted sockets');
socket.emit('jobResult', { result: 'emitting startup!' });
});
}
return SocketConnection.instance;
}
}
Then in my app.ts, I use it like this:
// app.ts
// express app
const server = app.listen(param);
import { SocketConnection } from './integrationJobs/socketConnection';
const io = SocketConnection.getInstance(server);
// Now pass the resulting "io" into the job where I need to use socket.io
import { CourseListJob } from './integrationJobs/courseList';
const courseListJob = new CourseListJob(io);
const job = courseListJob.runJob();
Last bit of code where I call the job needing a socket:
// courseList.ts
export class CourseListJob {
io: any;
public constructor(io) {
this.io = io;
}
public async runJob() {
this.io.emit('progressMessage', 'Hello');
}
}
Unfortunately, I get this:
TypeError: this.io.emit is not a function
What am I doing wrong?
I solved this by modifying my SocketConnection class to this:
import * as sio from 'socket.io';
export class SocketConnection {
private static instance: SocketConnection;
server: any;
private constructor(server) {
this.server = server;
}
public static GetSocket(server) {
const io = sio(server);
io.on('connection', (socket) => {
console.log('conncted SocketConnection sockets');
socket.emit('jobResult', { result: 'emitting startup!' });
});
return io;
}
}
The in my app.ts, I use it like this:
import { SocketConnection } from './integrationJobs/socketConnection';
const io = SocketConnection.GetSocket(server);
import * as courseList from './integrationJobs/courseList';
courseList.register(io);
import * as courseEnrollment from './integrationJobs/courseEnrollment';
courseEnrollment.register(io);
In any bit of code where I need a socket, I just do this:
import * as sio from 'socket.io';
let io = null;
export function register(foo) {
io = foo;
}
export class CourseListJob {
public async runJob() {
}
}

Resources