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
Related
I have an app that's making heavy use of Hapi's Server Methods. The methods are being applied via an exported register function, and the code that's being executed for the method is in the same file and thus not exported. I'm trying to write tests for these methods without exporting the functions that they call in the simplest possible way, but I haven't found any examples of how to do so.
export function register(server) {
server.method(
'methodNameA',
async (path, {}) => {
// Some code here
return something; // I want to test this result
},
{
cache: methodCache.halfDay,
generateKey(path, { ... } = {}) {
return something;
},
}
);
};
Abstracting that logic is an option, but I'd rather not expose it just for a test. I'd also prefer to not test an entire route just to validate this bit of logic (though that may be the ultimate solution here).
I will use jestjs as my unit testing framework. You can provide a mocked server and a mocked implementation for server.method(). Then you can get the original method in your test case.
After getting the original method, test it as usual.
E.g.
register.ts:
export function register(server) {
server.method('methodNameA', async () => {
return 'something';
});
}
register.test.ts:
import { register } from './register';
describe('67093784', () => {
it('should pass', async () => {
let methodNameA;
const mServer = {
method: function mockImplementation(m, func) {
methodNameA = func;
},
};
register(mServer);
// test method
const actual = await methodNameA();
expect(actual).toEqual('something');
});
});
unit test result:
PASS examples/67093784/register.test.ts (11.981 s)
67093784
✓ should pass (3 ms)
-------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
register.ts | 100 | 100 | 100 | 100 |
-------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 14.766 s
Faker.js allow you to easily create faked data using for example the following:
import * as faker from 'faker'
console.log(faker.lorem.text())
So I tried to mock this library to spy the use of faker.lorem.text():
import * as faker from 'faker'
const mockFakerLoremText = jest.fn()
jest.mock('faker', () => ({
lorem: {
text: mockFakerLoremText
}
}))
it('should have called lorem.text() method', () => {
faker.lorem.text()
expect(mockFakerLoremText).toHaveBeenCalledTimes(1)
})
But then I got the following error:
ReferenceError: Cannot access 'mockFakerLoremText' before initialization
So has someone an idea how I can spy on the call of this method .lorem.text()?
From the docs Calling jest.mock() with the module factory parameter
A limitation with the factory parameter is that, since calls to jest.mock() are hoisted to the top of the file, it's not possible to first define a variable and then use it in the factory
That's why you got the error.
An working example using "jest": "^26.6.3":
index.test.js:
import * as faker from 'faker';
jest.mock('faker', () => ({
lorem: {
text: jest.fn(),
},
}));
it('should have called lorem.text() method', () => {
faker.lorem.text();
expect(faker.lorem.text).toHaveBeenCalledTimes(1);
});
unit test result:
PASS examples/65924623/index.test.ts
√ should have called lorem.text() method (3 ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 0 | 0 | 0 | 0 |
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 6.913 s, estimated 7 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 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
this is the code I have to test:
myFunction: function(data) {
var file = new Blob(data, {type: 'text/plain'});
window.open(window.URL.createObjectURL(file));
}
In order to test it, I thought to test if the window.open function is called, applying a 'spy' on window.open , in the following way:
sandbox.spy(window, 'open');
but, even leaving previous line as the unique line in the test, what I only get is the test failure and the following message:
global leaks detected: consoleLogging, open
Thus, in order to avoid that, I tried to re-define the function in the test in this way:
global.window = {
open: function (url) {}
};
In this case an exception raised:
Attempted to assign to readonly property
Then I tried to mock the 'open' via the following:
sandbox.mock(window, 'open');
objectUnderTest.myFunction();
expect(window.open.callCount).to.equal(1);
this way, I get no global or readonly errors, but an exception on the 'expect', telling that:
expected undefined to equal 1
Does someone knows a way to successfully test window.open?
Here is the unit test solution based on Node.js environment:
index.js:
const obj = {
myFunction: function(data) {
var file = new Blob(data, { type: 'text/plain' });
window.open(window.URL.createObjectURL(file));
}
};
module.exports = obj;
index.spec.js:
const obj = require('./');
const sinon = require('sinon');
const { expect } = require('chai');
describe('53524524', () => {
before(() => {
class Blob {}
global.Blob = Blob;
global.window = {
open() {},
URL: {
createObjectURL() {}
}
};
});
it('should test myFunction correctly', () => {
const openStub = sinon.stub(window, 'open');
const createObjectURLStub = sinon.stub(global.window.URL, 'createObjectURL').returns('fake object url');
obj.myFunction('fake data');
expect(createObjectURLStub.calledOnce).to.be.true;
expect(openStub.calledWith('fake object url')).to.be.true;
});
});
Unit test result with coverage report:
53524524
✓ should test myFunction correctly
1 passing (11ms)
---------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
---------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 66.67 | 100 | |
index.js | 100 | 100 | 100 | 100 | |
index.spec.js | 100 | 100 | 60 | 100 | |
---------------|----------|----------|----------|----------|-------------------|
Source code: https://github.com/mrdulin/mocha-chai-sinon-codelab/tree/master/src/stackoverflow/53524524