How to test electron ipc events using jest? - node.js

I'm working on some tests for an electron app I'm building. I'm running into the error below. I'm new to jest, so I imagine it's due to an incorrect setup. Any idea where I'm going wrong?
Error: Cannot find module 'ipcMain' from 'ipcMainEvents.spec.js'
//myEvents.ts
import { ipcMain } from 'electron'
export class IpcMainEvents {
constructor() {
ipcMain.on('openPlaywright', this.openPlaywright)
ipcMain.on('openPreviewBrowser', this.openPlaywright)
}
openPlaywright(event, arg) {
console.log('openPlaywright')
}
openPreviewBrowser(event, arg) {
console.log('openPreviewBrowser')
}
}
//myEvents.spec.ts
import {IpcMainEvents} from './ipcMainEvents'
import {ipcMain} from 'electron'
jest.mock('ipcMain')
describe('Should test the ipcMain events', () => {
let component;
let addSpy
beforeEach(()=>{
component = new IpcMainEvents()
})
it('should attach the eventListeners', () => {
expect(component.ipcMain.on.calls.all()[0].args[0]).toEqual('openPlaywright'); //<----Errors here
expect(component.ipcMain.on.calls.all()[1].args[0]).toEqual('openPreviewBrowser');
expect(component.ipcMain.on.calls.count()).toEqual(2);
});
});

Firstly, you should mock electron package, not ipcMain function.
Second, you should access the calls property of mocked function via .mock property.
E.g.
myEvents.ts:
import { ipcMain } from 'electron';
export class IpcMainEvents {
constructor() {
ipcMain.on('openPlaywright', this.openPlaywright);
ipcMain.on('openPreviewBrowser', this.openPlaywright);
}
openPlaywright(event, arg) {
console.log('openPlaywright');
}
openPreviewBrowser(event, arg) {
console.log('openPreviewBrowser');
}
}
myEvents.spec.ts:
import { IpcMainEvents } from './myEvents';
import { ipcMain } from 'electron';
jest.mock(
'electron',
() => {
const mockIpcMain = {
on: jest.fn().mockReturnThis(),
};
return { ipcMain: mockIpcMain };
},
{ virtual: true },
);
describe('Should test the ipcMain events', () => {
let component;
let addSpy;
beforeEach(() => {
component = new IpcMainEvents();
});
it('should attach the eventListeners', () => {
expect(ipcMain.on.mock.calls[0][0]).toEqual('openPlaywright');
expect(ipcMain.on.mock.calls[1][0]).toEqual('openPreviewBrowser');
expect(ipcMain.on.mock.calls).toHaveLength(2);
});
});
unit test results with coverage report:
PASS stackoverflow/61562193/myEvents.spec.js (10.657s)
Should test the ipcMain events
✓ should attach the eventListeners (3ms)
-------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-------------|---------|----------|---------|---------|-------------------
All files | 80 | 100 | 50 | 77.78 |
myEvents.js | 80 | 100 | 50 | 77.78 | 10,14
-------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 11.917s

Related

How to mock ElasticSearch client specific method

I have code in which I make a call to elasticsearch indices stats. Tried mocking it following way but did not work.
esIndicesStatsStub = sinon.stub(Client.indices.prototype, 'stats').returns({
promise: () => new Error()
})
The error I get is
TypeError: Cannot read property 'prototype' of undefined
I've source code like this
const { Client } = require('#elastic/elasticsearch')
const esClient = new Client({
node:'some https url'
})
esClient.indices.stats(params, function (err, data) {
if (err) {
reject(err);
} else {
if (data) {
//do something
}
//do some other thing
}
});
How do I mock elasticsearch properly with sinon? Because i'm not allowed to use #elastic/elasticsearch-mock
Since ES SDK defines some APIs such as indices via the getter function:
Object.defineProperties(API.prototype, {
//...
indices: {
get () { return this[kIndices] === null ? (this[kIndices] = new IndicesApi(this.transport)) : this[kIndices] }
},
//...
})
see v8.5.0/src/api/index.ts#L372
We should use stub.get(getterFn) API to replace a new getter for this stub.
For these APIs such as .bulk, .clearScroll which are assigned directly to API.prototype, you can call sinon.stub(Client.prototype, 'bulk') to stub them.
At last, since your code is defined in the module scope, the code will be executed when you import/require it. We should arrange our stubs first, then import/require the module. As you can see, I use dynamic import().
E.g.
index.ts:
import { Client } from '#elastic/elasticsearch';
const esClient = new Client({ node: 'http://localhost:9200' });
esClient.indices.stats({ index: 'a' }).then(console.log);
index.test.ts:
import sinon from 'sinon';
import { Client } from '#elastic/elasticsearch';
import { IndicesStatsResponse } from '#elastic/elasticsearch/lib/api/types';
describe('74949441', () => {
it('should pass', async () => {
const indicesStatsResponseStub: IndicesStatsResponse = {
_shards: {
failed: 0,
successful: 1,
total: 2,
},
_all: {
uuid: '1',
},
};
const indicesApiStub = {
stats: sinon.stub().resolves(indicesStatsResponseStub),
};
sinon.stub(Client.prototype, 'indices').get(() => indicesApiStub);
await import('./');
sinon.assert.calledWithExactly(indicesApiStub.stats, { index: 'a' });
});
});
Test result:
74949441
{
_shards: { failed: 0, successful: 1, total: 2 },
_all: { uuid: '1' }
}
✓ should pass (609ms)
1 passing (614ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
index.ts | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------
package versions:
"#elastic/elasticsearch": "^8.5.0",
"sinon": "^8.1.1",
"mocha": "^8.2.1",

How to mock and avoid express-validator calls in jest?

There is code in our codebase like below:
#Validate(Param1)
async post(request, responseHandler) {
// some code
}
I Am trying to test the post function. But want to avoid evaluating the #Validate function. The Validate is a function in another module.
// validator.ts
export const Validate = () => {
// some code
}
How to? .
You could use jest.mock(moduleName, factory, options) create the mocked Validate decorator instead of using the real Validate decorator which may have a lot of validation rules.
E.g.
index.ts:
import { Validate } from './validator';
export class Controller {
#Validate('params')
async post(request, responseHandler) {
console.log('real post implementation');
}
}
validator.ts:
export const Validate = (params) => {
return (target: any, propertyKey: string, descriptor: TypedPropertyDescriptor<any>) => {
const oFunc = descriptor.value;
descriptor.value = function inner(...args: any[]) {
console.log('real validator decorator implementation');
// lots of validation
const rval = oFunc.apply(this, args);
return rval;
};
};
};
index.test.ts:
import { Validate } from './validator';
import { mocked } from 'ts-jest/utils';
jest.mock('./validator');
describe('63531414', () => {
afterAll(() => {
jest.resetAllMocks();
});
it('should pass', async () => {
mocked(Validate).mockImplementationOnce((params) => {
return (target: any, propertyKey: string, descriptor: TypedPropertyDescriptor<any>) => {
const oFunc = descriptor.value;
descriptor.value = function inner(...args: any[]) {
console.log('mocked validator decorator implementation');
const rval = oFunc.apply(this, args);
return rval;
};
};
});
const { Controller } = require('./');
const logSpy = jest.spyOn(console, 'log');
const ctrl = new Controller();
await ctrl.post({}, () => {});
expect(Validate).toBeCalledWith('params');
expect(logSpy).toBeCalledWith('real post implementation');
});
});
unit test result with coverage report:
PASS src/stackoverflow/63531414/index.test.ts (12.634s)
63531414
✓ should pass (154ms)
console.log node_modules/jest-mock/build/index.js:860
mocked validator decorator implementation
console.log node_modules/jest-mock/build/index.js:860
real post implementation
--------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
--------------|----------|----------|----------|----------|-------------------|
All files | 45.45 | 100 | 25 | 45.45 | |
index.ts | 100 | 100 | 100 | 100 | |
validator.ts | 14.29 | 100 | 0 | 14.29 | 2,3,4,5,7,8 |
--------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 14.354s
source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/63531414

Mocking Date.Now jest toHaveBeenCalledWith in nestJs

I am trying to figure out how to mock a call to Date.now with jest in my nestjs application.
I have a repository method that soft deletes a resource
async destroy(uuid: string): Promise<boolean> {
await this.userRepository.update({ userUUID: uuid }, { deletedDate: Date.now() });
return true;
}
to soft delete we just add a timestamp of when it was requested to be deleted
Following some discussions on here and other sites I came up with this test.
describe('destroy', () => {
it('should delete a user schemas in the user data store', async () => {
const getNow = () => Date.now();
jest
.spyOn(global.Date, 'now')
.mockImplementationOnce(() =>
Date.now().valueOf()
);
const targetResource = 'some-uuid';
const result = await service.destroy(targetResource);
expect(result).toBeTruthy();
expect(userRepositoryMock.update).toHaveBeenCalledWith({ userUUID: targetResource }, { deletedDate: getNow() });
});
});
I assumed that .spyOn(global.Date) mocked the entire global dat function , but the Date.now() in my repository is still returning the actual date rather than the mock.
My question is, is there a way to provide the mock return value of Date.now called in the repository from the test or should I just DI inject a DateProvider to the repository class which I can then mock from my test?
jest.spyOn(Date, 'now') should work.
E.g.
userService.ts:
import UserRepository from './userRepository';
class UserService {
private userRepository: UserRepository;
constructor(userRepository: UserRepository) {
this.userRepository = userRepository;
}
public async destroy(uuid: string): Promise<boolean> {
await this.userRepository.update({ userUUID: uuid }, { deletedDate: Date.now() });
return true;
}
}
export default UserService;
userRepository.ts:
class UserRepository {
public async update(where, updater) {
return 'real update';
}
}
export default UserRepository;
userService.test.ts:
import UserService from './userService';
describe('60204284', () => {
describe('#UserService', () => {
describe('#destroy', () => {
it('should soft delete user', async () => {
const mUserRepository = { update: jest.fn() };
const userService = new UserService(mUserRepository);
jest.spyOn(Date, 'now').mockReturnValueOnce(1000);
const actual = await userService.destroy('uuid-xxx');
expect(actual).toBeTruthy();
expect(mUserRepository.update).toBeCalledWith({ userUUID: 'uuid-xxx' }, { deletedDate: 1000 });
});
});
});
});
Unit test results with 100% coverage:
PASS stackoverflow/60204284/userService.test.ts
60204284
#UserService
#destroy
✓ should soft delete user (9ms)
----------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
userService.ts | 100 | 100 | 100 | 100 |
----------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 5.572s, estimated 11s
source code: https://github.com/mrdulin/react-apollo-graphql-starter-kit/tree/master/stackoverflow/60204284

Assert/Expect in Mocha/Chai not working to catch throwing errors in constructor

I have a class that takes a buffer in the constructor like this
export class ZippedFileBlaBla {
zip: AdmZip;
constructor(zipData: Buffer) {
try {
this.zip = new AdmZip(zipData);
} catch (err) {
throw new ZipDecompressionError(`Invalid ZIP file, error: ${err}`);
}
}
}
I would like to test this class but I can seem to do it using neither Mocha or Chai. My test so far is
// this does not work
expect(() => new ZippedFileBlaBla(bufferOfInvalidFile)).to.throw(Error)
// this also does not work
assert.throws(function () {new ZippedFileBlaBla(bufferOfInvalidFile)}, Error)
Though of course when I run the unit tests the error is thrown and I do see it in the console...
Would appreciate any advice on what I am doing wrong here
You should use a stub or mock library to stub or mock the AdmZip class. So that you can control the instantiation process of AdmZip class throw an error or not.
E.g.
index.ts
import { AdmZip } from "./admZip";
import { ZipDecompressionError } from "./zipDecompressionError";
export class ZippedFileBlaBla {
zip: AdmZip;
constructor(zipData: Buffer) {
try {
this.zip = new AdmZip(zipData);
} catch (err) {
throw new ZipDecompressionError(`Invalid ZIP file, error: ${err}`);
}
}
}
admZip.ts:
export class AdmZip {
constructor(zipData: Buffer) {}
}
zipDecompressionError.ts:
export class ZipDecompressionError extends Error {
constructor(msg: string) {
super(msg);
}
}
index.test.ts:
import { ZippedFileBlaBla } from "./";
import * as admZip from "./admZip";
import sinon from "sinon";
import { expect } from "chai";
describe("59686738", () => {
afterEach(() => {
sinon.restore();
});
it("should throw zip decompression error", () => {
const AdmZipStub = sinon.stub(admZip, "AdmZip").callsFake(() => {
throw new Error("some error");
});
const bufferOfInvalidFile = Buffer.from([1]);
expect(() => new ZippedFileBlaBla(bufferOfInvalidFile)).to.throw(Error);
sinon.assert.calledWithExactly(AdmZipStub, bufferOfInvalidFile);
});
it("should create adm zip", () => {
const mZip = {};
const AdmZipStub = sinon.stub(admZip, "AdmZip").callsFake(() => mZip);
const bufferOfInvalidFile = Buffer.from([1]);
const instance = new ZippedFileBlaBla(bufferOfInvalidFile);
expect(instance.zip).to.be.eql(mZip);
sinon.assert.calledWithExactly(AdmZipStub, bufferOfInvalidFile);
});
});
Unit test results with coverage report:
59686738
✓ should throw zip decompression error
✓ should create adm zip
2 passing (10ms)
--------------------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
--------------------------|----------|----------|----------|----------|-------------------|
All files | 100 | 50 | 92.31 | 100 | |
admZip.ts | 100 | 100 | 50 | 100 | |
index.test.ts | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
zipDecompressionError.ts | 100 | 50 | 100 | 100 | 3 |
--------------------------|----------|----------|----------|----------|-------------------|
Source code: https://github.com/mrdulin/mocha-chai-sinon-codelab/tree/master/src/stackoverflow/59686738

Mocha: testing function used in constructor

I am using mocha, chai and sinon for my testing purposes. I've got a class like below:
class ClassToTest {
person;
constructor(person) {
this.setPerson(person);
}
setPerson(person) {
if (typeof person.firstName === 'undefined') {
throw Error('Person has to have first name.');
}
this.person = person;
}
}
How I can test setPerson function? When I create new ClassToTest object setPerson gets called by constructor and Error gets thrown immediately. I've tried creating stubs with Sinon but no luck so far.
Should I even test setPerson function to begin with? I was thinking about:
1. moving validation (typeof if) to other function (e.g. validatePerson) and test that
2. testing only if constructor throws Error or sets person
Here is the unit test solution:
index.ts:
export class ClassToTest {
person;
constructor(person) {
this.setPerson(person);
}
setPerson(person) {
if (typeof person.firstName === 'undefined') {
throw new Error('Person has to have first name.');
}
this.person = person;
}
}
index.test.ts:
import { ClassToTest } from './';
import sinon from 'sinon';
import { expect } from 'chai';
describe('57091171', () => {
afterEach(() => {
sinon.restore();
});
describe('#constructor', () => {
it('should set person', () => {
const setPersonStub = sinon.stub(ClassToTest.prototype, 'setPerson');
const person = { firstName: 'sinon' };
new ClassToTest(person);
sinon.assert.calledWithExactly(setPersonStub, person);
});
});
describe('#setPerson', () => {
it('should set person', () => {
const stub = sinon.stub(ClassToTest.prototype, 'setPerson').returns();
const person = { firstName: 'sinon' };
const ins = new ClassToTest(person);
stub.restore();
ins.setPerson(person);
expect(ins.person).to.deep.equal(person);
});
it('should handle error if firstName is not existed', () => {
const stub = sinon.stub(ClassToTest.prototype, 'setPerson').returns();
const person = {};
const ins = new ClassToTest(person);
stub.restore();
expect(() => ins.setPerson(person)).to.throw('Person has to have first name.');
});
});
});
unit test results with 100% coverage:
57091171
#constructor
✓ should set person
#setPerson
✓ should set person
✓ should handle error if firstName is not existed
3 passing (43ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
index.ts | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------

Resources