【问题标题】:Jest - mocking and testing pino multi streams based on log levelsJest - 基于日志级别模拟和测试 pino 多流
【发布时间】:2020-07-31 06:34:57
【问题描述】:

我正在努力寻找在测试日志服务中模拟和使用 pino 的正确方法, 所以这是我对 pino 记录器的实现。这会根据日志级别写入不同的文件流。

 getChildLoggerService(fileNameString): pino.Logger {
    const streams: Streams = [
      { level: 'fatal', stream: fs.createWriteStream(path.join(process.cwd(), './logs/database-connect-fatal.log'))},
      { level: 'error', stream: fs.createWriteStream(path.join(process.cwd(), './logs/database-connect-error.log'))},
      { level: 'debug', stream: fs.createWriteStream(path.join(process.cwd(), './logs/database-connect-debug.log'))},
      { level: 'info', stream: fs.createWriteStream(path.join(process.cwd(), './logs/database-connect-info.log'))},
    ];

    return  pino({useLevelLabels: true,
      base: {
        hostName: os.hostname(),
        platform: os.platform(),
        processId: process.pid,
        timestamp: this.appUtilService.getCurrentLocaleTimeZone(),
        // tslint:disable-next-line: object-literal-sort-keys
        fileName: this.appUtilService.getFileName(fileNameString),
      } ,
      level: this.appUtilService.getLogLevel(),
      messageKey: LOGGER_MSG_KEY,
      prettyPrint: this.appUtilService.checkForDevEnv(process.env.NODE_ENV),
      timestamp: () => {
        return this.appUtilService.getCurrentLocaleTimeZone()
      },

    }, multistream(streams)).child({
      connectorReqId: (process.env.REQ_APP_NAME === null ? 'local': process.env.REQ_APP_NAME)
        +uuid.v4().toString()
    });
  }

我想测试的最重要的部分是多流,我需要根据日志级别写入不同的日志文件,到目前为止我还想不出办法

import pino, { DestinationStream } from 'pino';
const sinon = require('sinon');
import pinoms from 'pino-multi-stream';
const fs = require('fs');
const path = require('path');
const stream = require('stream');
const { PassThrough } = require('stream');
class EchoStream extends stream.Writable {
    _write(chunk, enc, next) {
        console.log('ssdsdsd',chunk.toString());
        next();
    }
}
import * as _ from 'lodash';
import { Writable } from 'stream';
import { mocked } from 'ts-jest/utils';
import { LogServiceInstance } from './log.service';
// jest.mock('pino', () => jest.fn().mockImplementation(() => { ====> Tried this inline mock, doesnt work
//  return {
//      child: jest.fn().mockReturnValue(jest.requireActual('pino').Logger)
//  }
// }));
// jest.mock('pino', () => {
//  return jest.fn().mockImplementation(() => {
//      return {
//          child: jest.fn().mockReturnValue(jest.requireActual('pino').Logger),
//          stream: jest.fn().mockImplementation(() => {
//              return [
//                  {
//                      level: 'info',
//                      stream: fs.createWriteStream(
//                          path.resolve(process.cwd(), '/test/database-connector-logs/info.log')
//                      ),
//                  },
//                  {
//                      level: 'warn',
//                      stream: fs.createWriteStream(
//                          path.resolve(process.cwd(), '/test/database-connector-logs/warn.log')
//                      ),
//                  },
//              ];
//          }),
//      };
//  });
// });



describe('Test suite for Log service', () => {
    //const mockedPino = mocked(pino, true);
    test('Test case for getLoggerInstance', () => {
        const mockedPinoMsStream = [

        const mockedPinoStream = (pino.prototype.stream = jest.fn(() => mockedPinoMsStream));

        console.dir(pino);
        const prop = Reflect.ownKeys(pino).find((s) => {
            return s === 'symbols';
        });
    // Tried this but it did not work as the actual files are written with the values
        pino[prop]['streamSym'] = jest.fn().mockImplementation(() => {
            return fs.createWriteStream(path.resolve(process.cwd(), './test/database-connector-logs/info.log'))
        });
        console.dir(pino);
        const log = LogServiceInstance.getChildLoggerService(__filename);

        console.dir(Object.getPrototypeOf(log));
        log.info('test logging');
        expect(2).toEqual(2);

});

有人可以告诉我模拟错误的地方以及如何正确模拟它

更新:

我开始明白模拟 pino-multi-stream 可能会奏效,所以尝试了这种方式。这是在最顶部添加的,其余的所有模拟都被删除(即使在测试套件中也是如此)

const mockedPinoMultiStream  = {
    stream: jest.fn().mockImplementation(() => {
        return {write: jest.fn().mockReturnValue(new PassThrough())}
    })
}

jest.mock('pino-multi-stream', () => {
    return {
        multistream: jest.fn().mockReturnValue(mockedPinoMultiStream)
    }
});

想模拟测试是否基于级别,正在使用相应的命名文件,但这也会导致异常

TypeError: stream.write 不是函数

at Pino.write (/XXX/node_modules/pino/lib/proto.js:161:15)
at Pino.LOG (/XXXX/node_modules/pino/lib/tools.js:39:26)

最新更新:

所以我通过修改模拟 pino 多流的方式解决了异常

const { PassThrough } = require('stream');
...
...
const mockedPinoMultiStream  = {
  write: jest.fn().mockImplementation((data) => {
   return new Passthrough();
  })
};

现在不再有异常,并且当我打印“pino”时,write(method) 被正确地模拟了。但是我不明白如何根据不同的日志级别测试不同的文件。有人可以告诉我,这是怎么做的。? 注意:我尝试设置 fs.createWriteStream 的返回值而不是 Passthrough 但这不起作用

【问题讨论】:

    标签: node.js typescript jestjs ts-jest


    【解决方案1】:

    最后,我找到了使用基于不同日志级别的 pino 流的答案。 我继续创建了一个测试目录来存放测试日志文件。实际上,我们不希望 pino 掺杂实际的日志文件。所以我决定在笑话测试开始时模拟 pino 流。在触发任何测试套件之前,首先执行此文件。所以我修改了 package.json 中的 jest 配置,比如

    "setupFiles": [
          "<rootDir>/jest-setup/stream.logger.js"
        ],
    

    在 stream.logger.js 文件中,我添加了

    const pinoms = require('pino-multi-stream');
    const fs = require('fs');
    const path = require('path');
    const stream = require('stream');
    const Writable = require('stream').Writable;
    const { PassThrough } = require('stream');
    const pino = require('pino');
    class MyWritable extends Writable {
      constructor(options) {
        super(options);
      }
    
      _write(chunk, encoding, callback) {
        const writeStream =fs.createWriteStream(path.resolve(process.cwd(), './test/logs/info.log'));
        writeStream.write(chunk,'utf-8');
        writeStream.emit('close');
        writeStream.end();
      }
    }
    const mockedPinoMultiStream  = {
      write: jest.fn().mockImplementation((data) => {
        const writeStream = new MyWritable();
        return writeStream._write(data);
      })
    };
    jest.mock('pino-multi-stream', () => {
        return {
            multistream: jest.fn().mockReturnValue(mockedPinoMultiStream)
        }
    });
    

    现在我继续创建测试文件 - log.service.spec.ts

    import * as pino from 'pino';
    const sinon = require('sinon');
    import pinoms from 'pino-multi-stream';
    const fs = require('fs');
    const path = require('path');
    const stream = require('stream');
    
    
    import * as _ from 'lodash';
    import { Writable } from 'stream';
    import { mocked } from 'ts-jest/utils';
    import { LogServiceInstance } from './log.service';
    
    describe('Test suite for Log service', () => {
        //const mockedPino = mocked(pino, true);
        afterEach(() => {
            // delete the contents of the log files after each test suite
            fs.truncate((path.resolve(process.cwd(), './test/logs/info.log')), 0, () => {
                console.dir('Info log file deleted');
            });
    
            fs.truncate((path.resolve(process.cwd(), './test/logs/warn.log')), 0, () => {
                console.dir('Warn log file deleted');
            });
            fs.truncate((path.resolve(process.cwd(), './test/logs/debug.log')), 0, () => {
                console.dir('Debug log file deleted');
            });
        });
        test('Test case fir getLoggerInstance', () => {
            const pinoLoggerInstance = LogServiceInstance.getChildLoggerService(__filename);
            pinoLoggerInstance.info('test logging');
    
            _.map(Object.getOwnPropertySymbols(pinoLoggerInstance), (mapItems:any) => {
                if(mapItems.toString().includes('Symbol')) {
                    if(mapItems.toString().includes('pino.level')) {
                        expect(pinoLoggerInstance[mapItems]).toEqual(20);
                    }
                }
                if(mapItems.toString().includes('pino.chindings')) {
                    const childInstance = pinoLoggerInstance[mapItems].toString().substr(1);
                    const jsonString  = '{'+ childInstance+ '}';
                    const expectedObj = Object.create(JSON.parse(jsonString));
                    expect(expectedObj.fileName).toEqual('log.service.spec');
                    expect(expectedObj.appName).toEqual('AppJestTesting');
                    expect(expectedObj.connectorReqId).toEqual(expect.objectContaining(new String('AppJestTesting')));
                }
            });
            // make sure the info.log file is written in this case
            const infoBuffRead = fs.createReadStream(path.resolve(process.cwd(), './test/logs/info.log')).read(1024);
            expect(infoBuffRead).toBeDefined();
    
            // now write a warn log
            pinoLoggerInstance.warn('test warning log');
            const warnBuffRead = fs.createReadStream(path.resolve(process.cwd(), './test/logs/warn.log')).read(1024);
            expect(warnBuffRead).toBeDefined();
    
    
            // now write a debug log
            pinoLoggerInstance.debug('test warning log');
            const debugBuffRead = fs.createReadStream(path.resolve(process.cwd(), './test/logs/warn.log')).read(1024);
            expect(debugBuffRead).toBeDefined();
    
    
        });
    });
    

    我还通过在每次执行后删除其内容来确保测试日志文件不会随着时间的推移而被数据淹没 希望这可以帮助人们尝试测试 pino 多流

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-12-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-01
      • 2021-08-04
      • 2011-04-15
      • 1970-01-01
      相关资源
      最近更新 更多