Unit testing Hapi Server Methods - javascript

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

Related

Use Jest to test appendfilesync

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

How to spy on a fakerjs method call

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

Unit test class constructor and method with Jest

I'm trying to figure out how to perform unit tests, with Jest, on my code but I have been struggling with this. I want to be able to test the constructor and the method in my unit test but I can't understand how to do it in an efficient way.
class CommandOption {
constructor(commands) {
this.section = commands[0]
this.method = commands[1]
this.command1 = commands[2]
}
option(optionName) {
return require(`../commands/options/${optionName}`)(this)
}
}
I know that I can test the constructor fairly easily, in this case, but I don't know if it is good way or not.
const CommandOption = require('./command-option')
it('should equal "hardware", "get", and "byid"', () => {
let commandOption = new CommandOption(['hardware','get','byid'])
expect(commandOption.section).toBe('hardware')
expect(commandOption.method).toBe('get')
expect(commandOption.command1).toBe('byid')
}
I don't really know how to go about mocking the option method from there... I have read about using jest.spyOn() but I can't seem to wrap my head around it for my case... probably because I am trying to overthink it.
Unit test solution:
command-option.js:
class CommandOption {
constructor(commands) {
this.section = commands[0];
this.method = commands[1];
this.command1 = commands[2];
}
option(optionName) {
return require(`./commands/options/${optionName}`)(this);
}
}
module.exports = CommandOption;
command-option.test.js:
const CommandOption = require('./command-option');
describe('64327189', () => {
it('should equal "hardware", "get", and "byid"', () => {
let commandOption = new CommandOption(['hardware', 'get', 'byid']);
expect(commandOption.section).toBe('hardware');
expect(commandOption.method).toBe('get');
expect(commandOption.command1).toBe('byid');
});
it('should load option by name', () => {
const optionSetter = jest.fn();
jest.doMock('./commands/options/host', () => optionSetter);
let commandOption = new CommandOption(['hardware', 'get', 'byid']);
const optionName = 'host';
commandOption.option(optionName);
expect(optionSetter).toBeCalledWith(commandOption);
});
});
commands/options/host.js:
// whatever
unit test result with coverage report:
PASS src/stackoverflow/64327189/command-option.test.js
64327189
✓ should equal "hardware", "get", and "byid" (4ms)
✓ should load option by name (3ms)
-------------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-------------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
command-option.js | 100 | 100 | 100 | 100 | |
-------------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 4.848s, estimated 10s

Mock the return value of an imported function in Typescript with Jest

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

How to mock a library function call in typescript?

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

Categories

Resources