Mocking/stubbing a class with Jest in Node.JS - node.js

I've got a class that sends a Slack message that looks like this:
class SlackMessage {
constructor(email_address, message) {
this.email_address = email_address;
this.message = message;
this.slackClient = new WebClient(token);
}
async send() {
let user = await this.user();
return await this.slackClient.chat.postMessage({
channel: user.user.id,
text: message,
});
}
async user() {
return await this.slackClient.users.lookupByEmail({
email: email_address
});
}
}
I want to mock the slackClient, so that slackClient.users.lookupByEmail returns a known value, and I can check this.slackClient.chat.postMessage gets called with the expected args, but I can't figure out how to do this. I've tried this (and various other permuations):
jest.mock("#slack/web-api");
describe("SlackMessage", () => {
beforeAll(() => {
WebClient.mockImplementation(() => {
return {
chat: {
postMessage: jest.fn(() => {
return Promise.resolve({})
})
},
users: {
lookupByEmail: jest.fn(() => {
return Promise.resolve({
user: {
id: "1234"
}
})
})
}
};
});
});
test("it sends a slack message", async () => {
message = new SlackMessage("foo#example.com", "Hello there!");
message.send()
expect(message.slackClient.users.lookupByEmail).toHaveBeenCalledWith({
email: "foo#example.com"
})
expect(message.slackClient.chat.postMessage).toHaveBeenCalledWith({
channel: "1234",
text: "Hello there!",
})
})
})
But this gives me the error:
expect(jest.fn()).toHaveBeenCalledWith(...expected)
Expected: {"email": "foo#example.com"}
Number of calls: 0
33 | message.send()
34 |
> 35 | expect(message.slackClient.users.lookupByEmail).toHaveBeenCalledWith({
| ^
36 | email: "foo#example.com"
37 | })
38 | expect(message.slackClient.chat.postMessage).toHaveBeenCalledWith({
at Object.<anonymous> (spec/slack_message.test.js:35:53)
Where am I going wrong?

Related

Trying to run unit testing for a findAll() function. But getting this error " expect(received).toEqual(expected) // deep equality "

Error message is this.
` PASS src/app.controller.spec.ts
FAIL src/users/services/users.service.spec.ts
● UsersService › should return all users
expect(received).toEqual(expected) // deep equality
- Expected - 6
+ Received + 1
- Object {
- "birthDate": 2010-06-07T00:00:00.000Z,
- "email": "xs#gsuj.lk",
- "id": 2,
- "name": "aq1s",
- }
+ Object {}
66 | //const dto ={id:2, name:'aq1s',birthDate: new Date('2010-06-07'), email : "xs#gsuj.lk" };
67 |
> 68 | expect( await service.findAll()).toEqual({
| ^
69 | id:2,
70 | name:'aq1s',
71 | birthDate: new Date('2010-06-07'),
at src/users/services/users.service.spec.ts:68:38
at fulfilled (src/users/services/users.service.spec.ts:5:58)
FAIL src/users/controllers/users.controller.spec.ts (5.09 s)
● UsersController › should findall
expect(received).toEqual(expected) // deep equality
- Expected - 6
+ Received + 1
- Object {
- "birthDate": 2010-06-07T00:00:00.000Z,
- "email": "xs#gsuj.lk",
- "id": 2,
- "name": "aq1s",
- }
+ Object {}
129 | const data = await controller.findAll();
130 |
> 131 | expect(data).toEqual({
| ^
132 | id: dto.id,
133 | name: dto.name,
134 | birthDate: dto.birthDate,
at src/users/controllers/users.controller.spec.ts:131:18
at fulfilled (src/users/controllers/users.controller.spec.ts:5:58)
Test Suites: 3 failed, 1 passed, 4 total
Tests: 2 failed, 9 passed, 11 total
Snapshots: 0 total
Time: 5.85 s
Ran all test suites.`
Users.controller.spec.ts file is this
describe('UsersController', () => {
let controller: UsersController;
const mockUsersService = {
create: jest.fn((dto) => {
return {
...dto,
};
}),
update: jest.fn((id, dto) => ({
id,
...dto,
})),
delete: jest.fn((id) => ({
id,
})),
findAll: jest.fn((dto) => {
return {
...dto,
};
}),
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [UsersController],
providers: [UsersService],
})
.overrideProvider(UsersService)
.useValue(mockUsersService)
.compile();
//get service module from user module
controller = module.get<UsersController>(UsersController);
});
//check if service available
it('should be defined', () => {
expect(controller).toBeDefined();
});
it('should create', async () => {
const dto = {
id: 2,
name: 'aqs',
birthDate: new Date('2000-06-07'),
email: 'xs#gsuj.lk',
};
const data = await controller.create(dto);
expect(data).toEqual({
id: dto.id,
name: dto.name,
birthDate: dto.birthDate,
email: dto.email,
});
expect(controller).toBeDefined();
});
it('should findall', async () => {
const dto = {
id: 2,
name: 'aq1s',
birthDate: new Date('2010-06-07'),
email: 'xs#gsuj.lk',
};
const data = await controller.findAll();
expect(data).toEqual({
id: dto.id,
name: dto.name,
birthDate: dto.birthDate,
email: dto.email,
}),
expect(controller).toBeDefined();
});
});
user.service.spec.ts file is this.
describe('UsersService', () => {
let service: UsersService;
// let providers:UsersController;
const mockUsersRepository ={
create:jest.fn().mockImplementation(dto =>dto),
save:jest.fn().mockImplementation(user => Promise.resolve({id:2, ...user})),
update:jest.fn().mockImplementation((id,user) => Promise.resolve({id:2, ...user})),
delete:jest.fn().mockImplementation(id => Promise.resolve({id:2})),
find:jest.fn().mockImplementation(() => ({})),
}
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [UsersService, {
provide:getRepositoryToken(User),
useValue:mockUsersRepository,
},
],
}).compile();
service = module.get<UsersService>(UsersService);
});
it('should be create a new user record and return that', async () => {
expect( await service.create({id:2, name:'aqs',birthDate: new Date('2000-06-07'), email : "xs#gsuj.lk" })).toEqual({
id:2,
name:'aqs',
birthDate: new Date('2000-06-07'),
email : "xs#gsuj.lk" ,
});
});
it('should return all users', async () => {
//const dto ={id:2, name:'aq1s',birthDate: new Date('2010-06-07'), email : "xs#gsuj.lk" };
expect( await service.findAll()).toEqual({
id:2,
name:'aq1s',
birthDate: new Date('2010-06-07'),
email : "xs#gsuj.lk" ,
});
});
});
users.controller file is this
#Get()
async findAll() {
const users = await this.userService.findAll();
if (!users) {
return 'error while finding users';
}
return users;
}
users.service file is this
findAll(): Promise<User[]> {
return this.userRepository.find()
}
Fixed the issue by, user.controller.spec.ts
it('should find all', async () => {
const data = await controller.findAll();
expect(data).toEqual({}),
expect(controller).toBeDefined();
});
And user.service.spec.ts:
it('should return all users', async () => {
expect( await service.findAll()).toEqual({});
});

How to use jest to keep track of how many times a nested function was called

Inside my test I want to be able to expect that analyzeArticle was called a set number of times. I need to mock the analyzation controller because it does a lot of async things that aren't important to this unit test.
const analyzeArticle = require('./analyzationController.js')
module.exports.consumer = async (event) => {
try {
const analyzationPromises = []
for (const record of event.Records) {
console.log('attempting to analyze: ', record.body)
analyzationPromises.push(analyzeArticle(record.body))
}
await Promise.allSettled(analyzationPromises)
} catch (err) {
console.log('reached top level with error: ', err)
}
}
My test looks like this
const consumer = require('../../analyzer/handler').consumer
const mockAnalyzeArticle = require('../../analyzer/analyzationController')
jest.mock('../../analyzer/analyzationController')
it('calls analyzer on all records', async () => {
await consumer({ Records: [{ body: 'fake body1' }, { body: 'fake body2' }] })
expect(mockAnalyzeArticle).toHaveBeenCalledTimes(2)
})
I get this error
Matcher error: received value must be a mock or spy function
Received has type: function
Received has value: [Function analyzeArticle]
6 | it('calls analyzer on all records', async () => {
7 | await consumer({ Records: [{ body: 'fake body1' }, { body: 'fake body2' }] })
> 8 | expect(mockAnalyzeArticle).toHaveBeenCalledTimes(2)
| ^
9 | })
10 |

mtproto/core Telegram replay to another Channel

i want collect news from different channels and echo them in a second channel, with the code i can read channels(not all but most).
I stuck now on the echo problem and have no clue about how i can do this, mtproto is completely new to me, thanks.
Im using the following code i have from another stackoverflow question.
const { MTProto, getSRPParams } = require('#mtproto/core');
const prompts = require('prompts');
const api_id = 'xxxxx';
const api_hash = 'xxxxx';
async function getPhone() {
return (await prompts({
type: 'text',
name: 'phone',
message: 'Enter your phone number:'
})).phone
}
async function getCode() {
// you can implement your code fetching strategy here
return (await prompts({
type: 'text',
name: 'code',
message: 'Enter the code sent:',
})).code
}
async function getPassword() {
return (await prompts({
type: 'text',
name: 'password',
message: 'Enter Password:',
})).password
}
const mtproto = new MTProto({
api_id,
api_hash,
});
function startListener() {
console.log('[+] starting listener')
mtproto.updates.on('updates', ({ updates }) => {
const newChannelMessages = updates.filter((update) => update._ === 'updateNewChannelMessage').map(({ message }) => message) // filter `updateNewChannelMessage` types only and extract the 'message' object
for (const message of newChannelMessages) {
// printing new channel messages
console.log(`[${message.to_id.channel_id}] ${message.message}`)
}
});
}
// checking authentication status
mtproto
.call('users.getFullUser', {
id: {
_: 'inputUserSelf',
},
})
.then(startListener) // means the user is logged in -> so start the listener
.catch(async error => {
// The user is not logged in
console.log('[+] You must log in')
const phone_number = await getPhone()
mtproto.call('auth.sendCode', {
phone_number: phone_number,
settings: {
_: 'codeSettings',
},
})
.catch(error => {
if (error.error_message.includes('_MIGRATE_')) {
const [type, nextDcId] = error.error_message.split('_MIGRATE_');
mtproto.setDefaultDc(+nextDcId);
return sendCode(phone_number);
}
})
.then(async result => {
return mtproto.call('auth.signIn', {
phone_code: await getCode(),
phone_number: phone_number,
phone_code_hash: result.phone_code_hash,
});
})
.catch(error => {
if (error.error_message === 'SESSION_PASSWORD_NEEDED') {
return mtproto.call('account.getPassword').then(async result => {
const { srp_id, current_algo, srp_B } = result;
const { salt1, salt2, g, p } = current_algo;
const { A, M1 } = await getSRPParams({
g,
p,
salt1,
salt2,
gB: srp_B,
password: await getPassword(),
});
return mtproto.call('auth.checkPassword', {
password: {
_: 'inputCheckPasswordSRP',
srp_id,
A,
M1,
},
});
});
}
})
.then(result => {
console.log('[+] successfully authenticated');
// start listener since the user has logged in now
startListener()
});
})

unit test in Nodejs using Jest

I have a method in controller, as below:
import { calcu } from '../services/myServices';
export const getProduct = (req, res, next) => {
try {
const { type } = req.params;
const { id, productCode } = req.body;
if (!id || !productCode) {
res.status(400).json({ error: { message: 'Id or productCode is required' } });
} else {
switch (type.toUpperCase()) {
case 'X':
try {
const result = calcu(id, productCode);
res.status(200).json(result);
} catch (err) {
res.status(400).json({ error: { message: err.message } });
}
break;
default:
res.status(400).json({ error: { message: `type ${type} is not support` } });
}
}
} catch (err) {
next(err);
}
};
This my unit test code in this case:
import { getProduct } from './quotationController';
describe('Controller', () => {
let json, res, status;
test('Should return message error if the id or productCode is missing', () => {
const req = {
body: { id: "1111" },
param: { type: "qqqqq" }
};
const next = err => err.message;
const result = getProduct(req, res, next);
//expect(result).toBe(undefined);
expect(result).toEqual({
code: 400,
message: 'Id or productCode is required'
});
});
})
I got an error when I ran the unit test code:
result is undefined.
Here is the unit test solution:
controller.js:
import { calcu } from './service';
export const getProduct = (req, res, next) => {
try {
const { type } = req.params;
const { id, productCode } = req.body;
if (!id || !productCode) {
res.status(400).json({ error: { message: 'Id or productCode is required' } });
} else {
switch (type.toUpperCase()) {
case 'X':
try {
const result = calcu(id, productCode);
res.status(200).json(result);
} catch (err) {
res.status(400).json({ error: { message: err.message } });
}
break;
default:
res.status(400).json({ error: { message: `type ${type} is not support` } });
}
}
} catch (err) {
next(err);
}
};
service.js: (simulated)
export function calcu(id, code) {
return id + code;
}
controller.test.js:
import { getProduct } from './controller';
import { calcu } from './service';
jest.mock('./service.js', () => ({ calcu: jest.fn() }));
describe('Controller', () => {
let mRes;
let mNext;
beforeEach(() => {
mRes = { status: jest.fn().mockReturnThis(), json: jest.fn() };
mNext = jest.fn();
});
afterEach(() => {
jest.resetAllMocks();
});
test('Should return message error if the id or productCode is missing', () => {
const mReq = { body: { id: '1111' }, params: { type: 'qqqqq' } };
getProduct(mReq, mRes, mNext);
expect(mRes.status).toBeCalledWith(400);
expect(mRes.status().json).toBeCalledWith({ error: { message: 'Id or productCode is required' } });
});
test('should call next when error happens', () => {
const mReq = {};
getProduct(mReq, mRes, mNext);
expect(mNext).toBeCalledWith(expect.any(Error));
});
test('should return message error if type is not support', () => {
const mReq = { params: { type: 'qqqqq' }, body: { id: '1111', productCode: '22' } };
getProduct(mReq, mRes, mNext);
expect(mRes.status).toBeCalledWith(400);
expect(mRes.status().json).toBeCalledWith({ error: { message: `type ${mReq.params.type} is not support` } });
});
test('should return message error if calcu errors', () => {
const mReq = { params: { type: 'x' }, body: { id: '1111', productCode: '22' } };
const mError = new Error('calc error');
calcu.mockImplementationOnce(() => {
throw mError;
});
getProduct(mReq, mRes, mNext);
expect(calcu).toBeCalledWith('1111', '22');
expect(mRes.status).toBeCalledWith(400);
expect(mRes.status().json).toBeCalledWith({ error: { message: mError.message } });
});
test('should return correct calc result', () => {
const mReq = { params: { type: 'x' }, body: { id: '1111', productCode: '22' } };
calcu.mockReturnValueOnce({ data: 'fake data' });
getProduct(mReq, mRes, mNext);
expect(calcu).toBeCalledWith('1111', '22');
expect(mRes.status).toBeCalledWith(200);
expect(mRes.status().json).toBeCalledWith({ data: 'fake data' });
});
});
Unit test result with 100% coverage:
PASS src/stackoverflow/59508494/controller.test.js (7.379s)
Controller
✓ Should return message error if the id or productCode is missing (6ms)
✓ should call next when error happens (1ms)
✓ should return message error if type is not support (1ms)
✓ should return message error if calcu errors (2ms)
✓ should return correct calc result (2ms)
---------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
---------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
controller.js | 100 | 100 | 100 | 100 | |
---------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 5 passed, 5 total
Snapshots: 0 total
Time: 8.731s, estimated 10s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/59508494

How to mock response from service for testing controller in typescript using Jest

I am testing a controller written in typescript using Jest. I try to mock the response of the service, but it does not work out.
this is my EmployeesController
import { EmployeesService } from '../services/employeeService';
import { IDBConnection } from '../config/IDBConnection';
export class EmployeesController {
private employeeService: EmployeesService;
constructor(dbConnection: IDBConnection) {
this.employeeService = new EmployeesService(dbConnection);
}
public async findAllEmployees(req: any, res: any) {
const numPerPage = +req.query.pagesize;
const page = +req.query.page;
try {
const count = await this.employeeService.findCount();
const results = await this.employeeService.findAll(numPerPage, page);
let totalEmployee = count[0].totalCount;
if (totalEmployee === 0) {
return res.status(404).json({
success: false,
message: 'Employee not found'
});
} else if (count && results) {
return res.status(200).json({
employees: results,
maxEmployees: totalEmployee
});
};
} catch {
res.status(500).json({
success: false,
message: 'Server error'
});
};
}
this is my EmployeesService
import { IDBConnection } from '../config/IDBConnection';
export class EmployeesService {
private connection: any;
constructor(connection: IDBConnection) {
this.connection = connection;
}
async findCount() {
const results = await this.connection.execute('SELECT count(*) as totalCount FROM EmployeeDB.Employees');
return results; // [ RowDataPacket { totalCount: 5 } ]
}
}
I can assume I am piping to it incorrectly from my service in test but I am not too sure. Is anyone able to help me?
this is my Employee.test
jest.mock('../../../services/employeeService');
import { EmployeesController } from '../../../controllers/employeeController';
import { EmployeesService } from '../../../services/employeeService';
describe('Employees', () => {
test('should get count of employees', async () => {
const getCount = jest.spyOn(EmployeesService.prototype, "findCount")
.mockImplementation(() => Promise.resolve([{totalCount: 5}]));
const mockResp = () => {
const res: any = {}
res.status = jest.fn().mockReturnValue(res)
res.json = jest.fn().mockReturnValue(res)
return res
}
const mockReq = () => {
const req: any = {}
req.query = jest.fn().mockReturnValue(req);
return req
}
const req = mockReq({
pagesize: 1,
page: 0
});
const res = mockResp();
await EmployeesController.prototype.findAllEmployees(req, res);
expect(getCount).toHaveBeenCalledTimes(1); // Received number of calls: 0
}
}
Here is the unit test solution:
controllers/employeeController.ts:
import { EmployeesService } from '../services/employeeService';
import { IDBConnection } from '../config/IDBConnection';
export class EmployeesController {
private employeeService: EmployeesService;
constructor(dbConnection: IDBConnection) {
this.employeeService = new EmployeesService(dbConnection);
}
public async findAllEmployees(req: any, res: any) {
const numPerPage = +req.query.pagesize;
const page = +req.query.page;
try {
const count = await this.employeeService.findCount();
const results = await this.employeeService.findAll(numPerPage, page);
let totalEmployee = count[0].totalCount;
if (totalEmployee === 0) {
return res.status(404).json({
success: false,
message: 'Employee not found',
});
} else if (count && results) {
return res.status(200).json({
employees: results,
maxEmployees: totalEmployee,
});
}
} catch {
res.status(500).json({
success: false,
message: 'Server error',
});
}
}
}
services/employeeService.ts:
import { IDBConnection } from '../config/IDBConnection';
export class EmployeesService {
private connection: any;
constructor(connection: IDBConnection) {
this.connection = connection;
}
async findCount() {
const results = await this.connection.execute('SELECT count(*) as totalCount FROM EmployeeDB.Employees');
return results; // [ RowDataPacket { totalCount: 5 } ]
}
async findAll(numPerPage, page) {
return [];
}
}
config/IDBConnection.ts:
export interface IDBConnection {}
Employee.test.ts:
import { EmployeesController } from './controllers/employeeController';
import { EmployeesService } from './services/employeeService';
jest.mock('./services/employeeService', () => {
const mEmployeesService = {
findCount: jest.fn(),
findAll: jest.fn(),
};
return { EmployeesService: jest.fn(() => mEmployeesService) };
});
describe('Employees', () => {
afterEach(() => {
jest.resetAllMocks();
});
test('should get count of employees', async () => {
const mIDBConnection = {};
const employeeService = new EmployeesService(mIDBConnection);
(employeeService.findCount as jest.MockedFunction<any>).mockResolvedValueOnce([{ totalCount: 5 }]);
(employeeService.findAll as jest.MockedFunction<any>).mockResolvedValueOnce([{ id: 1, name: 'john' }]);
const mReq = {
query: {
pagesize: 10,
page: 1,
},
};
const mRes = {
status: jest.fn().mockReturnThis(),
json: jest.fn(),
};
const employeesController = new EmployeesController(mIDBConnection);
await employeesController.findAllEmployees(mReq, mRes);
expect(employeeService.findCount).toHaveBeenCalledTimes(1);
expect(employeeService.findAll).toBeCalledWith(10, 1);
expect(mRes.status).toBeCalledWith(200);
expect(mRes.status().json).toBeCalledWith({ employees: [{ id: 1, name: 'john' }], maxEmployees: 5 });
});
});
Unit test result with coverage report:
PASS src/stackoverflow/59235639/Employee.test.ts (11.243s)
Employees
✓ should get count of employees (13ms)
-----------------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-----------------------|----------|----------|----------|----------|-------------------|
All files | 88.89 | 66.67 | 100 | 86.67 | |
employeeController.ts | 88.89 | 66.67 | 100 | 86.67 | 18,29 |
-----------------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 12.958s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/59235639

Resources