Search code examples
node.jssinonpinojs

Stubbing pino logger as class instance


I have a custom log service, using a child instance of pinoLogger to log some things. Here is the code :

class LogService {
    ihmLogger = loggerPino.child({});

    async log(
        req: Request,
        level: number,
        message: string,
        additional?: string
    ): Promise<void> {
        //Context initialisation
        const logObj = this.initLogObj(
            req,
            ngxLoggerLevelToPinoLevel[level],
            message,
            additional
        );

        // Mapping log level with pino logger
        switch (ngxLoggerLevelToPinoLevel[level]) {
            case CONSTANTS.DEFAULT.LOG_NGX_LEVEL.ERROR:
                this.ihmLogger.error(logObj);
                break;
            case CONSTANTS.DEFAULT.LOG_NGX_LEVEL.WARM:
                this.ihmLogger.warn(logObj);
                break;
            case CONSTANTS.DEFAULT.LOG_NGX_LEVEL.INFO:
                this.ihmLogger.info(logObj);
                break;
            case CONSTANTS.DEFAULT.LOG_NGX_LEVEL.DEBUG:
                this.ihmLogger.debug(logObj);
                break;
        }
    }
    
    private initLogObj(
        req: Request,
        level: number,
        message: string,
        additional?: string
    ): ILog {
        const additionalObj = JSON.parse(additional || '{}');
        return {...};
    }
}

export default new LogService();

I'm trying to write unit-tests for this service. I'm using node-request-http to mock the Request object, successfully. My problem concerns this child element. I'm simply trying to see if the correct function is called depending on the level, but I can't access it correctly. Here are the few syntaxes I tried :

[...]
import logService from './log.service';
import { loggerPino } from '../utils/logger';
[...]


it("test 1", async () => {
    sinon.stub(loggerPino,'child').returns({
        error: sinon.spy()
    });
    await logService.log(request, 5, 'msg', JSON.stringify({}));

    console.log('A',logService.ihmLogger.error);
});
// >> A [Function: noop]

it("test 2", async () => {
    const logger = sinon.stub(logService, 'ihmLogger').value({
        error: sinon.spy()
    });
    await logService.log(request, 5, 'msg', JSON.stringify({}));

    console.log('B',logger.error);
});
// >> B undefined

it("test 3", async () => {
    const logger = sinon.stub(logService, 'ihmLogger').value({
        error: sinon.spy()
    });
    await logService.log(request, 5, 'msg', JSON.stringify({}));

    console.log('C',logService.ihmLogger.error);
});
// >> C [Function (anonymous)]

I never could access the "callCount" valut, let alone checking the given parameter with a getCall(0).args[0]

How should I write the stub to be able to correctly access the value i'm trying to test?


Solution

  • The solution here was to completely replace the ihmLogger property of the service, as done in the "B" try up there, but putting it in a variable and checking the variable, not the sinon.stub object returned :

    it("Log error called", async () => {
        const customLogger = {
            trace: sinon.spy(),
            debug: sinon.spy(),
            info: sinon.spy(),
            warn: sinon.spy(),
            error: sinon.spy(),
            fatal: sinon.spy()
        };
        sinon.stub(logService, 'ihmLogger').value(customLogger);
        await logService.log(request, 5, 'msg', JSON.stringify({}));
    
        expect(customLogger.error.callCount).to.eq(1);
        expect(customLogger.fatal.callCount).to.eq(0); //double check to avoid false positive
    });
    

    I'm not unit-testing the pinoLogger object (I have to trust if to work as intended), I'm unit-testing the correct branch & call. Doing this, I'm also able to inspect the logObj transmitted :

    const builtObj = customLogger.error.getCall(0).args[0]