I want to test a function (using jest) which takes a pino.Logger type object as an argument. I have failed at trying to mock it, so all I need is a noop Logger instance that does nothing. I'm mainly a Go dev and I don't have any problem defining the noop Zap logger or custom logger that implements Zap logger interface.
I tried mocking it like this:
import Pino from 'pino';
jest.mock('pino');
const childFakeLogger = { warn: jest.fn() };
const fakeLogger = {
child: jest.fn(() => childFakeLogger)
};
beforeAll(() => {
Pino.mockImplementation(() => fakeLogger); // err msg="Property 'mockImplementation' does not exist on type 'typeof pino'."
});
I've tried starting a noop definition like this:
import Pino, { Logger } from 'pino';
const noOp: Pino.LogFn = (..._args: any[]) => {};
export const noopLogger: Logger = {
child: any, // don't know how to define this
level: 'trace',
fatal: noOp,
error: noOp,
warn: noOp,
info: noOp,
debug: noOp,
trace: noOp,
};
Which gives the following error:
Type '{ level: string; fatal: Pino.LogFn; error: Pino.LogFn; warn: Pino.LogFn; info: Pino.LogFn; debug: Pino.LogFn; trace: Pino.LogFn; }' is not assignable to type 'Logger'.
Property 'silent' is missing in type '{ level: string; fatal: Pino.LogFn; error: Pino.LogFn; warn: Pino.LogFn; info: Pino.LogFn; debug: Pino.LogFn; trace: Pino.LogFn; }' but required in type 'BaseLogger'.
But I can't figure out how to fully define it according to the Pino.BaseLogger definition.
TL;DR don't make fakeloggers. Either make a real one that is silent or make a stub
Couple of things:
silent not the child property. silent is a log level and a log type (like debug, info, etc) and coincidently is a noop. so to get past the error you are specifically getting just add it to your noopLogger object:import Pino, { Logger } from 'pino';
const noOp: Pino.LogFn = (..._args: any[]) => {};
export const noopLogger: Logger = {
child: any, // don't know how to define this
level: 'trace',
silent: noOp, // this is the complaint
fatal: noOp,
error: noOp,
warn: noOp,
info: noOp,
debug: noOp,
trace: noOp,
};
child is a method that returns a Logger. so good luck making this recursiveimport type { EventEmitter } from 'node:events'
import type * as pino from 'pino'
export interface FullLogger<
CustomLevels extends string = never,
UseOnlyCustomLevels extends boolean = boolean
> extends EventEmitter {
// From BaseLogger
level: pino.LevelWithSilentOrString
fatal: pino.LogFn
error: pino.LogFn
warn: pino.LogFn
info: pino.LogFn
debug: pino.LogFn
trace: pino.LogFn
silent: pino.LogFn
// From LoggerExtras
readonly version: string
levels: pino.LevelMapping
useLevelLabels: boolean
levelVal: number
child<ChildCustomLevels extends string = never>(
bindings: pino.Bindings,
options?: pino.ChildLoggerOptions<ChildCustomLevels>
): pino.Logger<CustomLevels | ChildCustomLevels>
onChild: pino.OnChildCallback<CustomLevels>
on(
event: 'level-change',
listener: pino.LevelChangeEventListener<CustomLevels, UseOnlyCustomLevels>
): this
addListener(
event: 'level-change',
listener: pino.LevelChangeEventListener<CustomLevels, UseOnlyCustomLevels>
): this
once(
event: 'level-change',
listener: pino.LevelChangeEventListener<CustomLevels, UseOnlyCustomLevels>
): this
prependListener(
event: 'level-change',
listener: pino.LevelChangeEventListener<CustomLevels, UseOnlyCustomLevels>
): this
prependOnceListener(
event: 'level-change',
listener: pino.LevelChangeEventListener<CustomLevels, UseOnlyCustomLevels>
): this
removeListener(
event: 'level-change',
listener: pino.LevelChangeEventListener<CustomLevels, UseOnlyCustomLevels>
): this
isLevelEnabled(level: pino.LevelWithSilentOrString): boolean
bindings(): pino.Bindings
setBindings(bindings: pino.Bindings): void
flush(cb?: (err?: Error) => void): void
}
so what i do is just avoid it :-P. Instead I propose you do this:
If you want to disable logging during tests, configure your logger like this:
import pino from 'pino'
const logger = pino({
level: process.env.NODE_ENV === 'test' ? 'silent' : 'info',
})
now there is no need to mock them to shut them up.
You should really only be mocking the logger (like you were with your noopLogger) when:
In those cases, what you want to do is create a stub like this:
import { vi } from 'vitest'
import type { Logger } from 'pino'
const logger = {
// just define the properties and methods that get called
info: () => {},
error: () => {},
} as Logger
vi.spyOn(logger, 'info')
myFunctionThatLogs(logger)
expect(logger.info).toHaveBeenCalledWith('expected message')
LMK if you have further questions or if i was unclear on anything