I have a javascript class that contains a function that starts a cron job using the node-cron library.
But I can't seem to find a way to test the class and the function.
The variables that control the node-cron are in a .env file.
To do the testing I'm using jest.
My job is started when the app.js of my node application is started(the class file is imported and is export instantiated)
.env file
#CRON JOB
FREQUENCY_CRON='30 00 * * *'
TIMEZONE="America/Sao_Paulo"
SCHEDULED=true
INACTIVE_EXPIRATION_TIME=2592000000 #30 DAYS
CronJob.js
class CronJob {
constructor() {
this.startJob();
}
async startJob() {
cron.schedule(
process.env.FREQUENCY_CRON,
async () => {
//DO SOME DATA PROCESSING
},
{
scheduled: process.env.SCHEDULED,
timezone: process.env.TIMEZONE
}
);
}
}
export default new CronJob();
You could use jest.mock(moduleName, factory, options) to mock node-cron module. Use dotenv package load the environment variables from .env before running the test cases.
E.g.
cronJob.js:
import cron from 'node-cron';
class CronJob {
constructor() {
this.startJob();
}
async startJob() {
cron.schedule(
process.env.FREQUENCY_CRON,
async () => {
console.log('DO SOME DATA PROCESSING');
},
{
scheduled: process.env.SCHEDULED,
timezone: process.env.TIMEZONE,
},
);
}
}
export default new CronJob();
cronJob.test.js:
import cron from 'node-cron';
import path from 'path';
require('dotenv').config({ path: path.resolve(__dirname, './.env') });
jest.mock('node-cron', () => {
return {
schedule: jest.fn(),
};
});
describe('61765291', () => {
it('should pass', () => {
const logSpy = jest.spyOn(console, 'log');
cron.schedule.mockImplementationOnce(async (frequency, callback) => await callback());
require('./cronJob');
expect(logSpy).toBeCalledWith('DO SOME DATA PROCESSING');
expect(cron.schedule).toBeCalledWith('30 00 * * *', expect.any(Function), {
scheduled: 'true',
timezone: 'America/Sao_Paulo',
});
});
});
.env:
#CRON JOB
FREQUENCY_CRON='30 00 * * *'
TIMEZONE="America/Sao_Paulo"
SCHEDULED=true
INACTIVE_EXPIRATION_TIME=2592000000 #30 DAYS
unit test results with 100% coverage:
PASS stackoverflow/61765291/cronJob.test.js (8.148s)
61765291
✓ should pass (30ms)
console.log
DO SOME DATA PROCESSING
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 | 100 | 100 | 100 | 100 |
cronJob.js | 100 | 100 | 100 | 100 |
------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 9.145s, estimated 10s
Related
I am trying to write a test for appendfilesync. I call this when using a logger and so I have 1 line of code not covered. Here is the code (note: using tslog for logger)
export function logToTransport(logObject: ILogObject) {
appendFileSync('monopoly_deal.log', JSON.stringify(logObject) + '\n');
}
I've tried mocking up 'fs' which can work so that nothing actually writes to file but I don't actually test the writing. Here is start of my test code to set things up:
const log: Logger = new Logger({ name: 'card_types_test' });
log.attachTransport(
{
silly: CardTypes.logToTransport,
debug: CardTypes.logToTransport,
trace: CardTypes.logToTransport,
info: CardTypes.logToTransport,
warn: CardTypes.logToTransport,
error: CardTypes.logToTransport,
fatal: CardTypes.logToTransport,
},
'info',
);
// Here I would need to setup a jest.spyon I think to intercept the call but not sure how to do it. Something like const spy = jest.fn(CardTypes.logToTransport);
log.info('test'); // Logs to file
expect(spy).toequal({object with my data}); // I didn't put what object would actually look like for brevity.
Any guidance on how to mock up writing to files is greatly appreciated along with any other critiques (still a junior level programmer at best).
You should test the code logic of logToTransport function rather than fs.appendFileSync method. fs.appendFileSync is a built-in method of Node.js and it's well tested.
You can only mock the fs.appendFileSync method, this way is called mocking partials. We are going to test the implementation detail of logToTransport function, this test strategy is called white-box testing.
Besides, we can use jest.isMockFunction to verify if we do the mocking partials successfully.
E.g.
index.ts:
import { appendFileSync } from 'fs';
type ILogObject = any;
export function logToTransport(logObject: ILogObject) {
appendFileSync('monopoly_deal.log', JSON.stringify(logObject) + '\n');
}
index.test.ts:
import fs from 'fs';
import { logToTransport } from '.';
jest.mock('fs', () => ({
...(jest.requireActual('fs') as typeof fs),
appendFileSync: jest.fn(),
}));
describe('73466276', () => {
test('should pass', () => {
expect(jest.isMockFunction(fs.appendFileSync)).toBeTruthy();
expect(jest.isMockFunction(fs.appendFile)).toBeFalsy();
logToTransport({ level: 'debug', payload: { name: 'teresa teng' } });
expect(fs.appendFileSync).toBeCalledWith(
'monopoly_deal.log',
JSON.stringify({ level: 'debug', payload: { name: 'teresa teng' } }) + '\n'
);
});
});
Test result:
PASS stackoverflow/73466276/index.test.ts (11.306 s)
73466276
✓ should pass (3 ms)
----------|---------|----------|---------|---------|-------------------
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.012 s
I'm struggling to figure out how to do this.
example.js
import Logger from "logging-library";
export default function example() {
Logger.error(new Error("Example Error")):
}
example.test.js
test("will log an error", () => {
expect(Logger.error).toHaveBeenCalledWith(new Error("Example Error");
});
The examples I've found might cover mocking an entire library, but don't seem to cover mocking and also asserting how it was called.
unit test solution:
example.js:
import Logger from 'logging-library';
export default function example() {
Logger.error(new Error('Example Error'));
}
example.test.js:
import Logger from 'logging-library';
import example from './example';
jest.mock(
'logging-library',
() => {
return { error: jest.fn() };
},
{ virtual: true },
);
describe('64858662', () => {
afterAll(() => {
jest.resetAllMocks();
});
test('will log an error', () => {
example();
expect(Logger.error).toHaveBeenCalledWith(new Error('Example Error'));
});
});
unit test result:
PASS src/stackoverflow/64858662/example.test.js
64858662
✓ will log an error (5ms)
------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
example.js | 100 | 100 | 100 | 100 | |
------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 4.373s, estimated 12s
I have a code like this
import fun from '../../../example';
export async function init (props:any){
if (fun()){
doSomething();
}
}
I'm creating unit tests for this code above, but I actually want to mock the implementation of fun in the file only as I can't alter fun in its original file
You can use jest.mock(moduleName, factory, options) to mock ../../../example module.
E.g.
index.ts:
import fun from './example';
export async function init(props: any) {
if (fun()) {
console.log('doSomething');
}
}
example.ts:
export default function fun() {
console.log('real implementation');
return false;
}
index.test.ts:
import { init } from './';
import fun from './example';
import { mocked } from 'ts-jest/utils';
jest.mock('./example', () => jest.fn());
describe('63166775', () => {
it('should pass', async () => {
expect(jest.isMockFunction(fun)).toBeTruthy();
const logSpy = jest.spyOn(console, 'log');
mocked(fun).mockReturnValueOnce(true);
await init({});
expect(logSpy).toBeCalledWith('doSomething');
expect(fun).toBeCalledTimes(1);
logSpy.mockRestore();
});
});
unit test result:
PASS stackoverflow/63166775/index.test.ts (13.298s)
63166775
✓ should pass (33ms)
console.log
doSomething
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 | 100 | 50 | 100 | 100 |
index.ts | 100 | 50 | 100 | 100 | 4
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 15.261s
I have a function, lets call it generateName, which as you’ve guessed it, generates a name. The problem is that a new name is generated each time time a test is ran.
In one of my tests, I assert that a function is called with an object containing this name. However, the name keeps on changing. I could just check that the object has property name, but I don’t really want to do that.
My idea is that I can mock the return value of the generateName function and do something like this
Import { generateName } from ‘libs/generateName’
jest.fn(generateName).mockResolvedValue ( ‘hello’ )
expect ( spy ).toHaveBeenCalledWith (
expect.objectContaining ( {
name: 'houses',
} )
)
You can use jest.mock(moduleName, factory, options) to mock libs/generateName module.
E.g.
generateName.ts:
export async function generateName() {
const name = Math.random() + '';
return name;
}
main.ts:
import { generateName } from './generateName';
export function main() {
return generateName();
}
main.test.ts:
import { main } from './main';
import { generateName } from './generateName';
jest.mock('./generateName', () => {
return {
generateName: jest.fn(),
};
});
describe('61350152', () => {
it('should pass', async () => {
(generateName as jest.MockedFunction<typeof generateName>).mockResolvedValueOnce('hello');
const actual = await main();
expect(actual).toBe('hello');
});
});
unit test results with coverage report:
PASS stackoverflow/61350152/main.test.ts (28.524s)
61350152
✓ should pass (6ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
main.ts | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 31.98s
I am unable to mock 3rd party function call in typescript.
The third party library is moment-timezone and I want to mock the code to get browser timezone to write jest test.
Below is the code I need to mock and return string as 'Australia/Sydney'
moment.tz.guess()
I am trying to use jest.mock() as :-
jest.mock('moment-timezone', () => () => ({ guess: () => 'Australia/Sydney' }));
Here is the solution:
index.ts:
import moment from 'moment-timezone';
export function main() {
return moment.tz.guess();
}
index.spec.ts:
import { main } from './';
import moment from 'moment-timezone';
jest.mock('moment-timezone', () => {
const mTz = {
guess: jest.fn()
};
return {
tz: mTz
};
});
describe('main', () => {
test('should mock guess method', () => {
(moment.tz.guess as jest.MockedFunction<typeof moment.tz.guess>).mockReturnValueOnce('Australia/Sydney');
const actualValue = main();
expect(jest.isMockFunction(moment.tz.guess)).toBeTruthy();
expect(actualValue).toBe('Australia/Sydney');
expect(moment.tz.guess).toBeCalled();
});
});
Unit test result with 100% coverage:
PASS src/stackoverflow/58548563/index.spec.ts
main
✓ should mock guess method (6ms)
----------|----------|----------|----------|----------|-------------------|
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: 5.828s, estimated 19s