Jest mock return value of inner function of exported var - jestjs

report.api.js:
export var reportAPI = (function() {
function getIsProjectLeader() {
....
}
return {
getIsProjectLeader: getIsProjectLeader
};
}
workerReportForm.vue:
created() {
reportAPI.getIsProjectLeader().then((result) => {
xyz.spec.js:
import { reportAPI } from "#/api/report.api.js";
jest.mock('#/api/report.api.js')
describe("<worker-report-form>", () => {
it("should render the form", () => {
const wrapper = mount(WorkerReportForm, {
global: {},
propsData: {},
components: {},
});
expect(reportAPI.getIsProjectLeader).toBeCalledTimes(1);
});
});
I get:
● <worker-report-form> › should render the form
TypeError: Cannot read properties of undefined (reading 'then')
> 210 | reportAPI.getIsProjectLeader().then((result) => {
| ^
So how can I mock the return value of getIsProjectLeader() ?

Related

How mock a third-party module in Jest/NestJS?

I've the following code and I need to mock the connection.execute() function as this belongs to the third-party lib snowflake-sdk and is't not part of my unit test:
import * as SnowflakeSDK from 'snowflake-sdk';
import { Injectable } from '#nestjs/common';
#Injectable()
export class SnowflakeClient {
export(bucket: string, filename: string, query: string) {
return new Promise((resolve, reject) => {
const connection = this.getConnection();
const command = `COPY INTO '${bucket}${filename}' FROM (${query})`;
connection.execute({
sqlText: command,
complete: function (err) {
try {
if (err) {
return reject(err);
} else {
return resolve(true);
}
} finally {
connection.destroy(function (err) {
if (err) {
console.error('Unable to disconnect: ' + err.message);
}
});
}
},
});
});
}
getConnection() {
const connection = SnowflakeSDK.createConnection({
account: process.env.SNOWFLAKE_HOST!,
username: process.env.SNOWFLAKE_USERNAME!,
password: process.env.SNOWFLAKE_PASSWORD!,
role: process.env.SNOWFLAKE_ROLE!,
warehouse: process.env.SNOWFLAKE_WAREHOUSE!,
database: process.env.SNOWFLAKE_DATABASE!,
schema: process.env.SNOWFLAKE_SCHEMA!,
});
connection.connect(function (err: Error): void {
if (err) {
throw err;
}
});
return connection;
}
}
So I've created the following unit test:
import { SnowflakeClient } from 'src/snowflake/snowflake-client';
import { Test } from '#nestjs/testing';
describe('SnowflakeClient', () => {
let snowflakeClient: SnowflakeClient;
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
providers: [SnowflakeClient],
}).compile();
snowflakeClient = moduleRef.get<SnowflakeClient>(SnowflakeClient);
});
describe('export', () => {
it('should export a SQL query', async () => {
const connectionMock = jest.fn().mockImplementation(() => ({
execute: function (bucket: string, filename: string, sql: string) {
console.log(`${bucket}${filename}: ${sql}`);
},
}));
jest.spyOn(snowflakeClient, 'getConnection').mockImplementation(connectionMock);
const bucket = 's3://bucketName';
const filename = '/reports/test.csv';
const sql = 'select * from customers limit 100';
await expect(await snowflakeClient.export(bucket, filename, sql)).resolves.not.toThrow();
}, 10000);
});
});
But the connectionMock.execute isn't being called correctly as I'm getting the following error:
FAIL src/snowflake/snowflake-client.spec.ts (13.518 s)
SnowflakeClient
export
✕ should export a SQL query (10018 ms)
● SnowflakeClient › export › should export a SQL query
thrown: "Exceeded timeout of 10000 ms for a test.
Use jest.setTimeout(newTimeout) to increase the timeout value, if this is a long-running test."
14 |
15 | describe('export', () => {
> 16 | it('should export a SQL query', async () => {
| ^
17 | const connectionMock = jest.fn().mockImplementation(() => ({
18 | execute: function (bucket: string, filename: string, sql: string) {
19 | console.log(`${bucket}${filename}: ${sql}`);
at src/snowflake/snowflake-client.spec.ts:16:5
at src/snowflake/snowflake-client.spec.ts:15:3
at Object.<anonymous> (src/snowflake/snowflake-client.spec.ts:4:1)
I would like to test the SnowflakeClient.export() method but I need to mock the snowflake-sdk module as it isn't part of my code.
Anybody knows what I'm doing wrong?
When using sdk, it's better to pass them through the injection system of NestJs with a custom provider:
//////////////////////////////////
// 1. Provide the sdk in the module.
//////////////////////////////////
const SnowflakeConnectionProvider = {
provide: 'SNOWFLAKE_CONNECTION',
useFactory: () => {
const connection = SnowflakeSDK.createConnection({
account: process.env.SNOWFLAKE_HOST!,
username: process.env.SNOWFLAKE_USERNAME!,
password: process.env.SNOWFLAKE_PASSWORD!,
role: process.env.SNOWFLAKE_ROLE!,
warehouse: process.env.SNOWFLAKE_WAREHOUSE!,
database: process.env.SNOWFLAKE_DATABASE!,
schema: process.env.SNOWFLAKE_SCHEMA!,
});
connection.connect(function (err: Error): void {
if (err) {
throw err;
}
});
return connection;
},
inject: [],
}
#Module({
providers: [
SnowflakeClient,
SnowflakeConnectionProvider,
]
})
export class SnowflakeModule {}
//////////////////////////////////
// 2. Inject the connection into your `SnowflakeClient`
//////////////////////////////////
#Injectable()
export class SnowflakeClient {
constructor(#Inject('SNOWFLAKE_CONNECTION') private readonly connection: SnowflakeConnection) {}
// ...
}
//////////////////////////////////
// 3. Mock the dependency in tests
//////////////////////////////////
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
providers: [
{
provide: 'SNOWFLAKE_CONNECTION',
useValue: { /* Mock */},
},
SnowflakeClient,
],
}).compile();
snowflakeClient = moduleRef.get<SnowflakeClient>(SnowflakeClient);
});
You will have a much better control over the tests.
So I got this fixed doing this (but I'm not sure it's the best way):
const connectionMock = jest.createMockFromModule<Connection>('snowflake-sdk');
connectionMock.execute = jest.fn().mockImplementation(() => {
return Promise.resolve(null);
});
jest.spyOn(snowflakeClient, 'getConnection').mockImplementation(() => {
return connectionMock;
});
expect(snowflakeClient.export(bucket, filename, sql)).resolves.not.toThrow();
Let me know your thoughts :)

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

Unit test for customPollingHook which uses apollo useLazyQuery

So I have written a custom polling hook which uses useContext and useLazyQuery hooks. I want to write a unit test for this, which should cover its returned values state and side effect.
So far I have managed to do this much but I'm not so sure how to proceed ahead. Any tips?
export const useUploadActivityPolling = (
teId: TeIdType
): UploadActivityPollingResult => {
const { dispatch, uploadActivityId }: StoreContextType = useAppContext();
const [fetchActivityStatus, { error: UploadActivityError, data: UploadActivityData, stopPolling }] = useLazyQuery(
GET_UPLOAD_ACTIVITY,
{
pollInterval: 3000,
fetchPolicy: 'network-only',
variables: { teId, activityId: uploadActivityId },
}
);
useEffect(() => {
if (UploadActivityData) {
setUploadActivityId(
UploadActivityData.getUploadActivityStatus.activity_id,
dispatch
);
updateActivityStateAction(UploadActivityData.getExcelUploadActivityStatus.status, dispatch);
}
}, [UploadActivityData]);
return { fetchActivityStatus, stopPolling, UploadActivityError };
};
import React from 'react';
import { mount } from 'enzyme';
const TestCustomHook = ({ callback }) => {
callback();
return null;
};
export const testCustomHook = callback => {
mount(<TestCustomHook callback={callback} />);
};
describe('useUploadActivityPolling', () => {
let pollingResult;
const teId = 'some id';
beforeEach(() => {
testCustomHook(() => {
pollingResult = useUploadActivityPolling(teId);
});
});
test('should have an fetchActivityStatus function', () => {
expect(pollingResult.fetchActivityStatus).toBeInstanceOf(Function);
});
});

Testing custom hook with react-hooks-testing-library throws an error

I am trying to test a simple hook that fetches some data using axios. However the test is throwing a TypeError: "Cannot read property 'fetchCompanies' of undefined". Here's my custom hook (the full repo is here):
import { useState, useEffect } from 'react';
import { Company } from '../../models';
import { CompanyService } from '../../services';
export const useCompanyList = (): {
loading: boolean;
error: any;
companies: Array<Company>;
} => {
const [loading, setLoading] = useState(true);
const [error, setError] = useState();
const [companies, setCompanies] = useState<Array<Company>>([]);
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
const companies = await CompanyService.fetchCompanies();
// Sort by ticker
companies.sort((a, b) => {
if (a.ticker < b.ticker) return -1;
if (a.ticker > b.ticker) return 1;
return 0;
});
setCompanies(companies);
setLoading(false);
} catch (e) {
setError(e);
}
};
fetchData();
}, []);
return { loading, error, companies };
};
and here's my test:
import { renderHook } from 'react-hooks-testing-library';
import { useCompanyList } from './useCompanyList';
const companiesSorted = [
{
ticker: 'AAPL',
name: 'Apple Inc.'
},
...
];
jest.mock('../../services/CompanyService', () => {
const companiesUnsorted = [
{
ticker: 'MSFT',
name: 'Microsoft Corporation'
},
...
];
return {
fetchCompanies: () => companiesUnsorted
};
});
describe('useCompanyList', () => {
it('returns a sorted list of companies', () => {
const { result } = renderHook(() => useCompanyList());
expect(result.current.loading).toBe(true);
expect(result.current.error).toBeUndefined();
expect(result.current.companies).toEqual(companiesSorted);
});
});
Please help me understand how to use react-hooks-testing-library in this case.
Edit
This seems to be related to a Jest issue that was seemingly resolved. Please see https://github.com/facebook/jest/pull/3209.
The
TypeError: "Cannot read property 'fetchCompanies' of undefined"
is caused by the way you define the CompanyService service. In the code, you are exporting an object CompanyService with all the service methods. But in your test, you are mocking the CompanyService to return an object with the methods.
So, the mock should return a CompanyService object that is an object with all the methods:
jest.mock('../../services/CompanyService', () => {
const companiesUnsorted = [
{
ticker: 'MSFT',
name: 'Microsoft Corporation'
},
...
];
return {
CompanyService: {
fetchCompanies: () => companiesUnsorted
}
};
});
Now, once you solve this, you will find that you don't have the TypeError anymore but your test is not passing. That is because the code you are trying to test is asynchronous, but your test is not. So, immediately after you render your hook (through renderHook) result.current.companies will be an empty array.
You will have to wait for your promise to resolve. Fortunately, react-hooks-testing-library provides us a waitForNextUpdate function in order to wait for the next hook update. So, the final code for the test would look:
it('returns a sorted list of companies', async () => {
const { result, waitForNextUpdate } = renderHook(() => useCompanyList());
expect(result.current.loading).toBe(true);
expect(result.current.error).toBeUndefined();
expect(result.current.companies).toEqual([]);
await waitForNextUpdate();
expect(result.current.loading).toBe(false);
expect(result.current.error).toBeUndefined();
expect(result.current.companies).toEqual(companiesSorted);
});

Mocking Inherited Classes with Jest

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

Resources