Mocking TypeDI service with Jest - node.js

I'm using Node with TypeScript, TypeDI and Jest.
I'm creating services that depend on each other, let's say:
#Service()
export class MainService{
constructor(private secondService: SecondService){}
public someMethod(someString: string) // implementation depends on secondService
}
#Service()
export class SecondService{
constructor(private thirdService: ThirdService){}
}
#Service()
export class ThirdService{
constructor(){}
}
I want to test MainService, but to instantiate it I need to pass dependency and that dependency needs another dependecy.
I tried to do this like, it works, but is ugly:
const secondService = new SecondService(new ThirdService());
jest
.spyOn(secondService, "someMethod")
.mockImplementation((someString: string) => {
// do something
return something;
});
const mainService = new MainService(secondService);
// use mainService in tests
Of course creating new instance of dependency is not always an option, and defienetly not an option when it has many dependencies.
I think it should look more like:
const secondService = SomeMockFactory.create(SecondService);
but i can't find any way to create mock while cutting off dependencies. I tried using
const secondService = jest.genMockFromModule("path/to/second/service");
but after trying to spyOn secondService methods TS is throwing error that "someMethod" is not a function.
What am I missing / doing wrong? Do I need some other library than Jest?

After a while I found out how to do this using default jest behaviour.
First, you need to create mock of SecondService in path/to/second/service/__mocks__, like:
// path/to/second/service/__mocks__/SecondService.ts
const mock = jest.fn().mockImplementation(() => ({
async thingSecondServiceDoInFirstService(
param: number
): number {
return 1;
}));
export default mock;
SecondService has to be default export, like:
// path/to/second/service/SecondService.ts
#Service()
export default class SecondService {
constructor(private thirdService: ThirdService) {}
async thingSecondServiceDoInFirstService(
param: number
): number {
return this.thirdService.thingThirdServiceDoInSecond(param);
}
}
In test file you have to use jest.mock before importing SecondService, and then create SecondService instance from mock:
jest.mock("path/to/second/service/SecondService");
import SecondService from "path/to/second/service/SecondService";
import MainService from "path/to/main/service/MainService";
describe("Test main service", () => {
const SecondServiceMock = <jest.Mock<SecondService>>SecondService;
let secondService = new SecondServiceMock();
beforeEach(() => {
mainService = new MainService(secondService);
});
// test logic...
}
As requested, ThirdService is not needed anymore.

Related

How to spyOn an internal static function call?

I have the following event handler and I want to test that updateOrAddNew is being called.
const { Events } = require('client');
const User = require('../models/User');
module.exports = {
name: Events.MemberAdd,
async execute(member) {
User.updateOrAddNew(member);
}
}
I've written the following test:
import {it, expect, vi } from 'vitest';
import User from '../models/User';
import memberAdd from './memberAdd';
it('calls updateOrAddNew on memberAdd', async () => {
const spy = vi.spyOn(User, 'updateOrAddNew').mockReturnValue(true);
User.updateOrAddNew = spy;
const member = {...};
await memberAdd.execute(member);
expect(spy).toBeCalled();
});
I've tried numerous different syntax but the spy is never called. updateOrAddNew is a static method. How can I test whether it's being called when memberAdd.execute is run?
I'm pretty new to testing so any help would be appreciated.
What if you declare in that file globally like this:
jest.spyOn(User, 'updateOrAddNew').mockReturnValue(true);
This should update any usage of this method with mock

jest mock requires requireActual

Its unclear for me why requireActual (3) is required to use the mock in __mocks__/global.ts (2) instead of global.ts (1) inside app.ts
According to the docs https://jestjs.io/docs/jest-object#jestrequireactualmodulename it says
Returns the actual module instead of a mock, bypassing all checks on
whether the module should receive a mock implementation or not.
What are the mentioned checks?
// __mocks__/global.ts
export const globalConfig = {
configA: "mockedConfigA"
};
export const globalLibA = jest.fn((msg) => {
return msg + "+mockedLibA"
});
// app.ts
import { globalConfig, globalLibA } from "./global"
export const app = function (msg) {
console.log("Called app")
return globalLibA(msg);
}
export const globalConfigConfigA = globalConfig.configA;
Full source: https://github.com/Trenrod/testjest/tree/master/src

Import Module in ES6 Class Function

I have migrated my project to ESM and thus using .mjs in all my files in nodejs.
Previously in CommonJs, I could require a file right in the middle of a ES6 class function in order to load it only when needed.
module.exports = class Core{
constructor() {
this.init = this._init.bind(this)
return this.init()
}
async _init(){
const module = require('module')
//use required file/module here
}
}
But now when using Michael Jackson Scripts a.k.a .mjs, I cannot import a file on demand:
import Koa from 'koa'
export default = class Core{
constructor() {
this.init = this._init.bind(this)
return this.init()
}
async _init(){
import module from 'module'
//use imported file/module here
}
}
My app has many files/modules that are not consumed immediately, and can more can always be added in future, thus hardcoding the imports at the begining of the file is not an option.
Is there a way to import the files dynamically on demand when needed?
With a little modification from this answer, I managed to get it working via:
import Koa from 'koa'
export default = class Core{
constructor() {
this.init = this._init.bind(this)
return this.init()
}
async _init(){
const router = await import('./Router')
//use imported file/module here
}
}
Or you can use a promise if you are into that:
import('./router')
.then(something => {
//use imported module here
});
This suits me for now until the spec if finalised and shipped

How do you mock typeorm's getManager using testdouble?

When creating unit tests for typeorm, I want to mock my connection to the database so that I can run unit tests without actually ever connecting to the DB (a good thing!)
I see places where people have mocked typeorm's repositories using testdouble (which I am also using), but I am trying to do this with getManager and am having an issue figuring out how to make it work.
Here's an example. I have a class that, in the constructor, creates an EntityManager by using getManager() for a connection called 'test':
export class TestClass {
constructor() {
const test: EntityManager = getManager('test');
}
}
Now I want to test that I can simply create this class. Here's a sample (using mocha, chai, and testdouble):
describe('data transformer tests', () => {
it('can create the test class', () => {
// somehow mock getManager here
const testClass: TestClass = new TestClass();
chai.expect(testClass, 'could not create TestClass').to.not.be.null;
});
});
When I try this, I get this error message from typeorm:
ConnectionNotFoundError: Connection "test" was not found.
Here are some of the things I've tried to mock getManager:
td.func(getManager)
same error as above.
td.when(getManager).thenReturn(td.object('EntityMananger'));
gets the message:
Error: testdouble.js - td.when - No test double invocation call detected for `when()`.
Any ideas what the magic sauce is here for mocking getManager?
I tried sinon instead of testdouble. I created a small repository, which shows how you can mock database for your blazing unit-tests :)
I tried to cover all TypeORM test cases using Jest and Mocha
Example
import * as typeorm from 'typeorm'
import { createSandbox, SinonSandbox, createStubInstance } from 'sinon'
import { deepEqual } from 'assert'
class Mock {
sandbox: SinonSandbox
constructor(method: string | any, fakeData: any, args?: any) {
this.sandbox = createSandbox()
if (args) {
this.sandbox.stub(typeorm, method).withArgs(args).returns(fakeData)
} else {
this.sandbox.stub(typeorm, method).returns(fakeData)
}
}
close() {
this.sandbox.restore()
}
}
describe('mocha => typeorm => getManager', () => {
let mock: Mock
it('getAll method passed', async () => {
const fakeManager = createStubInstance(typeorm.EntityManager)
fakeManager.find.resolves([post])
mock = new Mock('getManager', fakeManager)
const result = await postService.getAll()
deepEqual(result, [post])
})
afterEach(() => mock.close())
})

Mocking node dependency for Typescript with Mocha / Sinon

I have a class which controls an audio receiver using an external library marantz-avr:
let AVReceiver = require('marantz-avr');
class MarantzPlugin {
hardwareInstance;
constructor(pluginConfiguration) {
const receiver = new AVReceiver(pluginConfiguration.settings.ip);
this.hardwareInstance = receiver;
}
turnPowerOn() {
this.hardwareInstance.setPowerState('on').then(res => res, error => console.error(error));
}
}
export default MarantzPlugin;
I want to unit test this class. In order to do so I have to mock the marantz-avr library since this library only works when an actual receiver is found on the provided ip address.
In the test below I mock the marantz-avr, however the MarantzPlugin still uses the original AVReceiver instead of the mocked one.
import { suite, test, slow, timeout } from 'mocha-typescript';
import * as mocha from 'mocha';
import * as assert from 'assert';
import * as sinon from 'sinon';
import * as should from 'should';
import MarantzPlugin from './';
let AVReceiver = require('marantz-avr');
#suite
class MarantzPluginTest {
public create() {
before(() => {
sinon.stub(AVReceiver.prototype, 'AVReceiver').callsFake(() => {
return {
setPowerState: () => {
return true;
}
}
});
});
let marantz = new MarantzPlugin({
id: 'MARANTZ',
settings: {
ip: '192.168.178.2',
}
});
marantz.setPowerState('on');
}
}
I've looked into http://sinonjs.org/how-to/link-seams-commonjs/ but when implemented it gave me Error: Cannot find module 'marantz-avr'
Does anyone see what I am missing here, or perhaps have a better way to unit test these kind of classes?

Resources