I have below mentioned files
|_ utils.js
|_methods.js
I am doing unit testing for rest.js methods, file contents are
methods.js
import Express from 'express'
import { add_rec } from './utils'
export const create_rec = () => async (req: Express.Request, res: Express.Response) => {
const rec_body = req.body.rec
return add_rec(rec_body)
.then((ret) => res.status(201).send(ret))
.catch((e) => {
res.status(500).send({ message: e.message })
})
}
How can mock the add_rec async function so that I can unit-test my create_rec
function
I am trying to test create_rec below way but it is not allowing me to mock add_rec method
mport { getMockReq, getMockRes } from '#jest-mock/express'
import { add_rec } from './utils'
jest.mock('./utils')
describe('test create_rec method valid param', () => {
it('test create_rec method', async () => {
const req = getMockReq({
body: {
rec: {},
},
})
const { res } = getMockRes<any>({
status: jest.fn(),
send: jest.fn(),
})
add_rec.mockResolved({}) // this line is giving error in fact it is not mocked i think
await create_rec()(req, res)
expect(res.status).toHaveBeenCalledTimes(1)
expect(res.send).toHaveBeenCalledTimes(1)
})
})
Please help me with this.
Your code is almost correct, except that you need to do some processing on the TS type of the mock method, you can use type assertion.
E.g.
methods.ts:
import Express from 'express';
import { add_rec } from './utils';
export const create_rec = () => async (req: Express.Request, res: Express.Response) => {
const rec_body = req.body.rec;
return add_rec(rec_body)
.then((ret) => res.status(201).send(ret))
.catch((e) => {
res.status(500).send({ message: e.message });
});
};
utils.ts:
export async function add_rec(params): Promise<any> {
console.log('real implementation');
}
methods.test.ts:
import Express from 'express';
import { create_rec } from './methods';
import { add_rec } from './utils';
jest.mock('./utils');
describe('68419899', () => {
test('should pass', async () => {
(add_rec as jest.MockedFunction<any>).mockResolvedValueOnce({});
const req = ({ body: { rec: {} } } as unknown) as Express.Request;
const res = ({ status: jest.fn().mockReturnThis(), send: jest.fn() } as unknown) as Express.Response;
await create_rec()(req, res);
expect(add_rec).toBeCalledWith({});
expect(res.status).toBeCalledWith(201);
expect(res.send).toBeCalledWith({});
});
});
test result:
PASS examples/68419899/methods.test.ts (8.659 s)
68419899
✓ should pass (5 ms)
------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
------------|---------|----------|---------|---------|-------------------
All files | 81.82 | 100 | 66.67 | 75 |
methods.ts | 88.89 | 100 | 80 | 83.33 | 10
utils.ts | 50 | 100 | 0 | 50 | 2
------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 9.198 s
Related
I'm currently attempting to mock AWS SecretsManager for my unit testing with Jest, and everytime I'm hit with the ConfigError
My code is somewhat like this
//index.ts
import SM from "aws-sdk/clients/secretsmanager"
const secretManagerClient = new SM()
...
export const randomMethod = async (a: string, b: string) => {
let secret
const personalToken = {
SecretId: process.env.secretId,
}
secretManagerClient
.getSecretValue(personalToken, (err, data) => {
if (err) {
console.error(`[SECRETS MANAGER] Error fetching personal token : ${err}`)
} else if (data && data.SecretString) {
secret = data.SecretString
}
})
}
My mock goes like this :
//index.test.js
const mockGetSecretValue = jest.fn((SecretId) => {
switch (SecretId) {
case process.env.GITHUB_PERSONAL_TOKEN:
return {
SecretString: process.env.GITHUB_PERSONAL_TOKEN_VALUE,
}
default:
throw Error("secret not found")
}
})
jest.mock("aws-sdk/clients/secretsmanager", () => {
return jest.fn(() => {
return {
getSecretValue: jest.fn(({ SecretId }) => {
return mockGetSecretValue(SecretId)
}),
promise: jest.fn(),
}
})
})
However, I get this error thrown at me : ConfigError: Missing region in config, which I understand to some extent, however I don't understand why it occurs here in the mocking part...
Thanks in advance!
EDIT: Thanks to the 1st answer, I've managed to stop having this error. However, the getSecretValue() method is not returning the Secret value I want.
You should NOT use the callback of .getSecretValue() method with .promise() together. Just choose one of them. The error means you didn't mock the secretsmanager class correctly of aws-sdk.
E.g.
index.ts:
import SM from 'aws-sdk/clients/secretsmanager';
const secretManagerClient = new SM();
export const randomMethod = async () => {
const personalToken = {
SecretId: process.env.secretId || '',
};
try {
const data = await secretManagerClient.getSecretValue(personalToken).promise();
return data.SecretString;
} catch (err) {
console.error(`[SECRETS MANAGER] Error fetching personal token : ${err}`);
}
};
index.test.ts:
import { randomMethod } from '.';
import SM from 'aws-sdk/clients/secretsmanager';
import { mocked } from 'ts-jest/utils';
import { PromiseResult } from 'aws-sdk/lib/request';
jest.mock('aws-sdk/clients/secretsmanager', () => {
const mSecretManagerClient = {
getSecretValue: jest.fn().mockReturnThis(),
promise: jest.fn(),
};
return jest.fn(() => mSecretManagerClient);
});
describe('69977310', () => {
test('should get secret value', async () => {
process.env.secretId = 's1';
const mSecretManagerClient = mocked<InstanceType<typeof SM>>(new SM());
const mGetSecretValueRequest = mocked(mSecretManagerClient.getSecretValue());
mGetSecretValueRequest.promise.mockResolvedValue({
SecretString: JSON.stringify({ password: '123456' }),
} as PromiseResult<any, any>);
const actual = await randomMethod();
expect(actual).toEqual(JSON.stringify({ password: '123456' }));
expect(mSecretManagerClient.getSecretValue as jest.Mocked<any>).toBeCalledWith({ SecretId: 's1' });
});
test('should throw error', async () => {
process.env.secretId = 's1';
const logSpy = jest.spyOn(console, 'error').mockImplementation(() => 'suppress error log for testing');
const mSecretManagerClient = mocked<InstanceType<typeof SM>>(new SM());
const mGetSecretValueRequest = mocked(mSecretManagerClient.getSecretValue());
const mError = new Error('network');
mGetSecretValueRequest.promise.mockRejectedValue(mError);
await randomMethod();
expect(logSpy).toBeCalledWith(`[SECRETS MANAGER] Error fetching personal token : ${mError}`);
expect(mSecretManagerClient.getSecretValue as jest.Mocked<any>).toBeCalledWith({ SecretId: 's1' });
});
});
test result:
PASS examples/69977310/index.test.ts (7.722 s)
69977310
✓ should get secret value (4 ms)
✓ should throw error (1 ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 50 | 100 | 100 |
index.ts | 100 | 50 | 100 | 100 | 6
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 8.282 s, estimated 10 s
package versions:
"aws-sdk": "^2.875.0",
"typescript": "^4.1.2",
"jest": "^26.6.3",
I've overlooked the fact that I was using a callback in order to bypass the promise().
The following is the correct code:
const mockGetSecretValue = jest.fn((SecretId, callback) => {
console.log("secretId", SecretId)
switch (SecretId) {
case process.env.GITHUB_PERSONAL_TOKEN:
const data = {
SecretString: process.env.GITHUB_PERSONAL_TOKEN_VALUE,
}
callback(null, data)
break;
default:
const err = new Error("secret not found")
throw err
}
})
jest.mock("aws-sdk/clients/secretsmanager", () => {
return jest.fn(() => {
return {
promise: jest.fn(),
getSecretValue: jest.fn(({ SecretId }, callback) => {
return mockGetSecretValue(SecretId, callback)
}),
}
})
})
Thanks again for your help #slideshowp2!
I have a DBManager class to connect to mongoClient
import { MongoClient } from 'mongodb';
class DBManager {
private url = process.env.MONGODB_URL;
private _connection: MongoClient;
constructor() {
this._connection = null;
}
get connection() {
return this._connection;
}
async start() {
if (!this._connection) {
this._connection = await MongoClient.connect(this.url);
}
}
}
export default new DBManager();
and I call this class like this
await DBManager.start();
const db = DBManager.connection.db();
I get this error when I try to mock:
Received: [TypeError: db_manager_1.default.connection.db is not a function]
this is how to mock method i use:
DBManager.start = jest.fn().mockResolvedValue(() => ({
connection: jest.fn().mockReturnThis(),
db: jest.fn().mockResolvedValue({success: true})
}));
thanks..
You can use a real MongoDB server to use in tests with the package mongodb-memory-server.
So in your case, you just need to do:
import { MongoMemoryServer } from '../index';
describe('Single MongoMemoryServer', () => {
let con;
let mongoServer;
beforeAll(async () => {
mongoServer = await MongoMemoryServer.create();
process.env.MONGODB_URL = mongoServer.getUri();
});
afterAll(async () => {
if (con) {
await con.close();
}
if (mongoServer) {
await mongoServer.stop();
}
});
it('DBManager connection', async () => {
await DBManager.start();
const db = DBManager.connection.db();
// ...
});
You can use jest.spyOn(object, methodName, accessType?) to mock DBManager.start() method and DBMananger.connection getter.
E.g.
dbManager.ts:
import { MongoClient } from 'mongodb';
class DBManager {
private url = process.env.MONGODB_URL || '';
private _connection: MongoClient | null;
constructor() {
this._connection = null;
}
get connection() {
return this._connection;
}
async start() {
if (!this._connection) {
this._connection = await MongoClient.connect(this.url);
}
}
}
export default new DBManager();
main.ts:
import DBManager from './dbManager';
export async function main() {
await DBManager.start();
const db = DBManager.connection!.db();
db.collection('users');
}
main.test.ts:
import { main } from './main';
import DBManager from './dbManager';
import { Db, MongoClient } from 'mongodb';
describe('68888424', () => {
afterEach(() => {
jest.restoreAllMocks();
});
test('should pass', async () => {
const mockDbInstance = ({
collection: jest.fn(),
} as unknown) as Db;
const mockDb = jest.fn(() => mockDbInstance);
jest.spyOn(DBManager, 'start').mockResolvedValueOnce();
jest.spyOn(DBManager, 'connection', 'get').mockReturnValue(({ db: mockDb } as unknown) as MongoClient);
await main();
expect(DBManager.start).toBeCalledTimes(1);
expect(DBManager.connection!.db).toBeCalledTimes(1);
expect(mockDbInstance.collection).toBeCalledWith('users');
});
});
test result:
PASS examples/68888424/main.test.ts (8.621 s)
68888424
✓ should pass (5 ms)
--------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
--------------|---------|----------|---------|---------|-------------------
All files | 75 | 50 | 50 | 75 |
dbManager.ts | 57.14 | 50 | 33.33 | 57.14 | 12-17
main.ts | 100 | 100 | 100 | 100 |
--------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 9.532 s
I am using Nestjs and have written the function below which recieves a file from a post request and saves it in a folder in my project.
My issue is I'm not sure how to test the on('error') branch.
function to unit test.
saveFile({ createReadStream, filename }: FileUpload): Promise<boolean> {
return new Promise(async (resolve, reject) => {
createReadStream().pipe(
createWriteStream(join(process.cwd(), `apps/mull-api/uploads/${filename}`))
.on('finish', () => resolve(true))
.on('error', () => {
console.log(createReadStream);
reject(false);
})
);
});
}
How I am testing the on('finish') branch
it('should save file', async () => {
const returnedFile = await service.saveFile(mockFile);
expect(returnedFile).toBe(true);
});
This is what my mockFile looks like. I tried providing a mockFile with empty name and it errors out.
export const mockFile: FileUpload = {
filename: 'zoro',
mimetype: 'image/jpeg',
encoding: '7bit',
createReadStream(): ReadStream {
return fs.createReadStream(join(process.cwd(), `apps/mull-api/uploads/mock-upload/zoro`));
},
};
We can mock createWriteStream, .on('finish') and .on('error') methods using mockImplementation(). And trigger these two events in the mock implementation function by ourself.
The 'finish' event handler in the mock implementation function is () => resolve(true); The 'error' event handler in the mock implementation function is () => reject(false);
See Mock Implementations, and below example:
const myMockFn = jest.fn(cb => cb(null, true));
myMockFn((err, val) => console.log(val));
// > true
index.ts:
import { createWriteStream, ReadStream } from 'fs';
import { join } from 'path';
export interface FileUpload {
filename: string;
mimetype: string;
encoding: string;
createReadStream(): ReadStream;
}
export class FileService {
public saveFile({ createReadStream, filename }: FileUpload): Promise<boolean> {
return new Promise(async (resolve, reject) => {
createReadStream().pipe(
createWriteStream(join(process.cwd(), `apps/mull-api/uploads/${filename}`))
.on('finish', () => resolve(true))
.on('error', () => {
reject(false);
}),
);
});
}
}
index.test.ts:
import { FileService, FileUpload } from './';
import { createWriteStream, WriteStream } from 'fs';
import { mocked } from 'ts-jest/utils';
jest.mock('fs');
describe('64485251', () => {
afterAll(() => {
jest.resetAllMocks();
jest.clearAllMocks();
});
it('should save file', async () => {
const mockReadStream = { pipe: jest.fn() };
const mockFile: FileUpload = {
filename: 'zoro',
mimetype: 'image/jpeg',
encoding: '7bit',
createReadStream: jest.fn().mockReturnValueOnce(mockReadStream),
};
const mockWriteStream = {
on: jest.fn().mockImplementation(function(this, event, handler) {
if (event === 'finish') {
handler();
}
return this;
}),
};
mocked(createWriteStream).mockReturnValueOnce((mockWriteStream as unknown) as WriteStream);
const service = new FileService();
const actual = await service.saveFile(mockFile);
expect(mockFile.createReadStream).toBeCalledTimes(1);
expect(mockReadStream.pipe).toBeCalledTimes(1);
expect(createWriteStream).toBeCalledWith(expect.stringContaining('apps/mull-api/uploads/zoro'));
expect(mockWriteStream.on).toBeCalledWith('finish', expect.any(Function));
expect(mockWriteStream.on).toBeCalledWith('error', expect.any(Function));
expect(actual).toBeTruthy();
});
it('should handle error if save file failed', async () => {
const mockReadStream = { pipe: jest.fn() };
const mockFile: FileUpload = {
filename: 'zoro',
mimetype: 'image/jpeg',
encoding: '7bit',
createReadStream: jest.fn().mockReturnValueOnce(mockReadStream),
};
const mockWriteStream = {
on: jest.fn().mockImplementation(function(this, event, handler) {
if (event === 'error') {
handler();
}
return this;
}),
};
mocked(createWriteStream).mockReturnValueOnce((mockWriteStream as unknown) as WriteStream);
const service = new FileService();
await expect(service.saveFile(mockFile)).rejects.toEqual(false);
expect(mockFile.createReadStream).toBeCalledTimes(1);
expect(mockReadStream.pipe).toBeCalledTimes(1);
expect(createWriteStream).toBeCalledWith(expect.stringContaining('apps/mull-api/uploads/zoro'));
expect(mockWriteStream.on).toBeCalledWith('finish', expect.any(Function));
expect(mockWriteStream.on).toBeCalledWith('error', expect.any(Function));
});
});
unit test result:
PASS src/stackoverflow/64485251/index.test.ts (10.201s)
64485251
✓ should save file (6ms)
✓ should handle error if save file failed (3ms)
----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 11.344s
I am stuck at mock one of my React component because of using one translation library call 'react-simple-i18n'
In my React component, i need import one function from this library to use it as below :
import { useI18n } from 'react-simple-i18n/lib/'
const MyComponent = ({ data }) => {
const { t } = useI18n()
return(
<div>{t('MyComponent.hello') }</div>
)
}
and if i try to test with Jest (simple snapshot)
import React from 'react'
import { shallow } from 'enzyme'
import MyComponent from './MyComponent'
import { useI18n } from 'react-simple-i18n'
const fakeData = { ... }
jest.mock('react-simple-i18n', () => {
useI18n: () => { t: 'test' }
})
let wrapper = shallow(<MyComponent data={fakeData}/>)
describe('MyComponent', () => {
it('should render MyComponent correctly', () => {
expect(wrapper).toMatchSnapshot();
})
})
And i get a fail from Jest :
TypeError: Cannot destructure property t of 'undefined' or 'null'.
How can i proprely mock my useI18n function ?
You can use jest.mock(moduleName, factory, options) to mock a library.
E.g.
index.jsx:
import { useI18n } from 'react-simple-i18n';
import React from 'react';
const MyComponent = ({ data }) => {
const { t } = useI18n();
return <div>{t('MyComponent.hello')}</div>;
};
export default MyComponent;
index.test.jsx:
import React from 'react';
import { shallow } from 'enzyme';
import MyComponent from './';
import { useI18n } from 'react-simple-i18n';
jest.mock(
'react-simple-i18n',
() => {
const mUseI18n = { t: jest.fn().mockReturnValue('test') };
return {
useI18n: jest.fn(() => mUseI18n),
};
},
{ virtual: true },
);
describe('MyComponent', () => {
it('should render MyComponent correctly', () => {
const fakeData = {};
let wrapper = shallow(<MyComponent data={fakeData} />);
expect(wrapper.text()).toBe('test');
expect(useI18n).toBeCalledTimes(1);
expect(useI18n().t).toBeCalledWith('MyComponent.hello');
});
});
unit test results with 100% coverage:
PASS stackoverflow/61083245/index.test.jsx (8.334s)
MyComponent
✓ should render MyComponent correctly (8ms)
-----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
index.jsx | 100 | 100 | 100 | 100 |
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 9.417s
source code: https://github.com/mrdulin/react-apollo-graphql-starter-kit/tree/master/stackoverflow/61083245
I am extending net.Socket. In doing so, I am overriding the connect method as follows.
class ENIP extends Socket {
constructor() {
super();
this.state = {
session: { id: null, establishing: false, established: false },
error: { code: null, msg: null }
};
}
connect(IP_ADDR) {
const { registerSession } = encapsulation; //returns a buffer to send
this.state.session.establishing = true;
return new Promise(resolve => {
super.connect(EIP_PORT, IP_ADDR, () => { // This is what i want to mock -> super.connect
this.state.session.establishing = false;
this.write(registerSession());
resolve();
});
});
}
}
I want to mock the underlying Socket class so that I can simulate super.connect. Having viewed Facebook's docs on the matter, I am unsure on how to proceed with developing tests for this class as all methods will extend super.someMethodToMock. Does anyone know an approach I should take? Please let me know if there are any clarifying details I can provide.
You can use jest.spyOn(object, methodName) to create mock methods on Socket.prototype.
E.g.
index.js:
import { Socket } from 'net';
const EIP_PORT = 3000;
const encapsulation = {
registerSession() {
return 'session';
},
};
export class ENIP extends Socket {
constructor() {
super();
this.state = {
session: { id: null, establishing: false, established: false },
error: { code: null, msg: null },
};
}
connect(IP_ADDR) {
const { registerSession } = encapsulation;
this.state.session.establishing = true;
return new Promise((resolve) => {
super.connect(EIP_PORT, IP_ADDR, () => {
this.state.session.establishing = false;
this.write(registerSession());
resolve();
});
});
}
}
index.test.js:
import { ENIP } from './';
import { Socket } from 'net';
describe('ENIP', () => {
afterAll(() => {
jest.restoreAllMocks();
});
describe('#connect', () => {
it('should pass', async () => {
const writeSpy = jest.spyOn(Socket.prototype, 'write').mockImplementation();
const connectSpy = jest.spyOn(Socket.prototype, 'connect').mockImplementationOnce((port, addr, callback) => {
callback();
});
const enip = new ENIP();
await enip.connect('localhost');
expect(writeSpy).toBeCalledWith('session');
expect(connectSpy).toBeCalledWith(3000, 'localhost', expect.any(Function));
});
});
});
unit test result with coverage report:
PASS src/stackoverflow/48888509/index.test.jsx (10.43s)
ENIP
#connect
✓ should pass (7ms)
-----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-----------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
index.jsx | 100 | 100 | 100 | 100 | |
-----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 12.357s
source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/48888509