How to test code inside setInterval with jest.useFakeTimers() - javascript

Here is my simple code myClass.js:
class myClass {
functionOne() {
setInterval(() => {
const age = 10
try {
const userName = "John Smith"
console.log(`Name: ${userName}, age: ${age}`)
} catch (error) {
logger.error('Error', error)
}
}, 5000)
}
}
module.exports = new myClass()
I'm trying to test it with JEST that works fine:
const myClass = require('./myClass')
test("test one", () => {
jest.useFakeTimers();
myClass.functionOne();
expect(setInterval).toHaveBeenCalledTimes(1);
expect(setInterval).toHaveBeenLastCalledWith(expect.any(Function), 5000);
jest.clearAllTimers();
});
Expects work fine and test pass. But sonar-scanner report says that the code inside setInterval() not covered by tests. So how do I really test code inside setInterval?

You can spy on console.log method and assert if it has been called or not. Besides, your example code will never throw an error, which means the catch statement block will never execute.
E.g.
myClass.js:
class myClass {
functionOne() {
setInterval(() => {
const age = 10;
try {
const userName = 'John Smith';
console.log(`Name: ${userName}, age: ${age}`);
} catch (error) {
console.error('Error', error);
}
}, 5000);
}
}
module.exports = new myClass();
myClass.test.js:
const myClass = require('./myClass');
describe('61902581', () => {
test('test one', () => {
jest.useFakeTimers();
const logSpy = jest.spyOn(console, 'log');
myClass.functionOne();
jest.advanceTimersByTime(5000);
expect(logSpy).toBeCalledWith('Name: John Smith, age: 10');
expect(setInterval).toHaveBeenCalledTimes(1);
expect(setInterval).toHaveBeenLastCalledWith(expect.any(Function), 5000);
jest.clearAllTimers();
logSpy.mockRestore();
});
});
unit test results with coverage report:
PASS stackoverflow/61902581/myClass.test.js (10.621s)
61902581
✓ test one (30ms)
console.log
Name: John Smith, age: 10
at CustomConsole.<anonymous> (node_modules/jest-environment-enzyme/node_modules/jest-mock/build/index.js:866:25)
------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
------------|---------|----------|---------|---------|-------------------
All files | 90 | 100 | 100 | 90 |
myClass.js | 90 | 100 | 100 | 90 | 9
------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 11.718s

Related

How to mock nanoid for testing?

I'm trying to mock nanoid for my testing but it doesn't seem to be working.
my function
public async createApp(appDto: ApplicationDto): Promise<string> {
const appWithToken = { ...appDto, accessToken: nanoid() };
const application = await this.applicationModel.create(appWithToken);
return application.id;
}
My test:
beforeEach(() => {
mockRepository.create.mockResolvedValueOnce({ id: mockId });
});
test("creates application and returns an id", async () => {
const mockAppDto: ApplicationDto = { email: "123#mock.com" };
const application = await applicationService.createApplication(mockAppDto);
expect(mockRepository.create).toHaveBeenCalledWith(mockAppDto); //how do I mock the nanoid here?
expect(application).toBe(mockId);
});
So basically I'm struggling to figure out how to mock the nanoid which is generated inside the function.
I've tried the following at the top of the file:
jest.mock('nanoid', () => 'mock id');
however it doesn't work at all.
Any help would be appreciated!
You didn't mock the nanoid module correctly. It uses named exports to export the nanoid function.
Use jest.mock(moduleName, factory, options) is correct, the factory argument is optional. It will create a mocked nanoid function.
Besides, you can use the mocked function from ts-jest/utils to handle the TS type.
E.g.
Example.ts:
import { nanoid } from 'nanoid';
export interface ApplicationDto {}
export class Example {
constructor(private applicationModel) {}
public async createApp(appDto: ApplicationDto): Promise<string> {
const appWithToken = { ...appDto, accessToken: nanoid() };
const application = await this.applicationModel.create(appWithToken);
return application.id;
}
}
Example.test.ts:
import { nanoid } from 'nanoid';
import { Example, ApplicationDto } from './Example';
import { mocked } from 'ts-jest/utils';
jest.mock('nanoid');
const mnanoid = mocked(nanoid);
describe('67898249', () => {
afterAll(() => {
jest.resetAllMocks();
});
it('should pass', async () => {
mnanoid.mockReturnValueOnce('mock id');
const mockAppDto: ApplicationDto = { email: '123#mock.com' };
const mockApplicationModel = { create: jest.fn().mockReturnValueOnce({ id: 1 }) };
const example = new Example(mockApplicationModel);
const actual = await example.createApp(mockAppDto);
expect(actual).toEqual(1);
expect(mockApplicationModel.create).toBeCalledWith({ email: '123#mock.com', accessToken: 'mock id' });
});
});
test result:
PASS examples/67898249/Example.test.ts (9.134 s)
67898249
✓ should pass (4 ms)
------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
Example.ts | 100 | 100 | 100 | 100 |
------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 10.1 s

Expect throwError not getting coverage

There is a ValidationError (custom made) being thrown on my invert function if no string is provided:
const invert = (str) => {
if (str === '') throw new ValidationError('String must no be empty.');
return str;
};
This line of code is not getting full coverage by my assertion while using Jest:
expect(() => invert('')).toThrow(ValidationError);
Is there a way to get coverage for this line?
You should have two test cases at least. One test throws an error, and one test is normal. E.g.
index.ts:
export class ValidationError extends Error {
constructor(message) {
super(message);
}
}
export const invert = (str) => {
if (str === '') throw new ValidationError('String must no be empty.');
return str;
};
index.test.ts:
import { invert, ValidationError } from './';
describe('64271662', () => {
it('should throw validation error if string is empty', () => {
expect(() => invert('')).toThrow(ValidationError);
});
it('should return string', () => {
expect(invert('teresa teng')).toBe('teresa teng');
});
});
unit test result with 100% coverage:
PASS src/stackoverflow/64271662/index.test.ts (9.867s)
64271662
✓ should throw validation error if string is empty (4ms)
✓ should return string (1ms)
----------|----------|----------|----------|----------|-------------------|
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.042s

How to mock moment() in jest testing

I need run some testing using jest and moment. Some of the imported functions work with moment current date (moment() ). But I can't seem to find a way of always run the same date for testing or mock moment constructor, like pin moment date at moment('2020-07-05'), even if current day is 2020-07-10, so the test should always run under day 5.
My ./UtilsModule file:
import moment from 'moment'
export const getIntervalDates = groupOfDates => {
const CURRENT_DATE = moment().format('YYYY-MM-DD');
return getDates(CURRENT_DATE, groupOfDates ) //another function that does some extra processing;
};
export const nextDates = (date, group) => {
let newDate= getIntervalDates(group);
}
My test.js file, and what i tried for:
import { nextDates ,getIntervalDates } from '../UtilsModule';
it('testing function', () => {
const PAYLOAD = {...};
const DATE= '2020-07-20';
const SpyGetIntervalDates = jest.spyOn(UtilsModule, 'getIntervalDates');
SpyGetIntervalDates.mockImplementation(() => Promise.resolve({ minAge: '2020-08-04' }));
const nextDate = UtilsModule.nextDates(DATE, PAYLOAD);
expect(nextDate).toEqual({ minDate: '2020-11-04' });
});
I also tried, but i couldn't make it to work :
jest.mock('moment', () => {
return () => jest.requireActual('moment')('2020-07-04');
});
and
global.moment = jest.fn(moment('2021-07-04'));
You were trying to test nextDates function with mocked getIntervalDates function. You need to do a little refactoring, you should keep the same reference for getIntervalDates function called inside nextDates. Then, you can use jest.spyOn to replace getIntervalDates with a mocked one.
E.g.
utilsModule.js:
import moment from 'moment';
function getDates() {}
const getIntervalDates = (groupOfDates) => {
const CURRENT_DATE = moment().format('YYYY-MM-DD');
return getDates(CURRENT_DATE, groupOfDates);
};
const nextDates = (date, group) => {
return exports.getIntervalDates(group);
};
exports.getIntervalDates = getIntervalDates;
exports.nextDates = nextDates;
utilsModule.test.js:
const UtilsModule = require('./utilsModule');
describe('62736904', () => {
it('testing function', async () => {
const PAYLOAD = {};
const DATE = '2020-07-20';
const SpyGetIntervalDates = jest.spyOn(UtilsModule, 'getIntervalDates');
SpyGetIntervalDates.mockImplementation(() => Promise.resolve({ minAge: '2020-08-04' }));
const nextDate = await UtilsModule.nextDates(DATE, PAYLOAD);
expect(nextDate).toEqual({ minAge: '2020-08-04' });
});
});
unit test result:
PASS stackoverflow/62736904/utilsModule.test.js (11.51s)
62736904
✓ testing function (3ms)
----------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------------|---------|----------|---------|---------|-------------------
All files | 75 | 100 | 33.33 | 75 |
utilsModule.js | 75 | 100 | 33.33 | 75 | 6-7
----------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 12.892s

Sinon stub function parameter

I have an express app with a router I would like to test with Sinon. I'm not successful in mocking the response parameter that is passed into the request handler and would like some help.
export const pingHandler = (request, response, next) => {
response.status(200).send('Hello world');
}
This is my current test setup using Mocha, Sinon, Chai & sinon-chai. fakeRes.status was never called as expected.
describe("pingHandler", () => {
it("should return 200", async () => {
const fakeResponse = {
status: sinon.fake(() => ({
send: sinon.spy()
}))
};
pingHandler({}, fakeResponse, {});
expect(fakeResponse.status).to.have.been.called;
// => expected fake to have been called at least once, but it was never called
});
});
Here is the unit test solution:
index.ts:
export const pingHandler = (request, response, next) => {
response.status(200).send('Hello world');
}
index.spec.ts:
import { pingHandler } from "./";
import sinon from "sinon";
describe("pingHandler", () => {
it("should return 200", () => {
const mRes = {
status: sinon.stub().returnsThis(),
send: sinon.stub(),
};
pingHandler({}, mRes, {});
sinon.assert.calledWith(mRes.status, 200);
sinon.assert.calledWith(mRes.send, "Hello world");
});
});
Unit test result with 100% coverage:
pingHandler
✓ should return 200
1 passing (8ms)
---------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
---------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
index.spec.ts | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
---------------|----------|----------|----------|----------|-------------------|

Chained API's and Jest expects

I currently have an express app that does a bunch of logic on a controller.
One of the steps is to insert a record to the DB ( It uses ObjectionJS models ).
let user = await this.User.query(trx).insert(userData);
In an attempt to mock out the model, I have done :
let mockUser = {
query: jest.fn(() => {
return mockUser;
}),
insert: jest.fn(() => {
return mockUser;
}),
toJSON: jest.fn()
};
With this, I wanted to do an assertion:
expect(mockUser.query().insert).toBeCalledWith({ some: 'data' });
It seems I have missed something. When I run the tests, the code would reach the mock function insert. But jest complaints
You could use mockFn.mockReturnThis() to return this context.
E.g.
index.js:
export async function main(User) {
const trx = 'the trx';
const userData = {};
let user = await User.query(trx).insert(userData);
return user.toJSON();
}
index.test.js:
import { main } from './';
describe('47953161', () => {
it('should pass', async () => {
let mockUser = {
query: jest.fn().mockReturnThis(),
insert: jest.fn().mockReturnThis(),
toJSON: jest.fn().mockResolvedValueOnce({ id: 1 }),
};
const actual = await main(mockUser);
expect(actual).toEqual({ id: 1 });
expect(mockUser.query).toBeCalledWith('the trx');
expect(mockUser.query().insert).toBeCalledWith({});
expect(mockUser.query().insert().toJSON).toBeCalledTimes(1);
});
});
unit test result with coverage report:
PASS src/stackoverflow/47953161/index.test.ts (10.41s)
47953161
✓ should pass (7ms)
----------|----------|----------|----------|----------|-------------------|
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: 1 passed, 1 total
Snapshots: 0 total
Time: 12.783s, estimated 13s
source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/47953161

Categories

Resources