代码之家  ›  专栏  ›  技术社区  ›  Learner

使用jest customFormat和默认printf测试记录器

  •  0
  • Learner  · 技术社区  · 6 年前

    我已经使用winston、morgan和winston daily rotate文件express http context实现了node js的记录器。因此,每天当http请求(morgan)或开发人员定义的记录器消息发送到该文件时,都会写入一个新的日志文件。从节点js。下面是如何记录的格式

    时间戳| | | | | | | |文件名| |跟踪ID | |状态码| |日志消息

    一切都很好,但当我用jest(初学者对jest)编写测试用例时。我不能讲两行。下面是完整的代码和文件夹结构。

    对于每个请求,我都要设置一个traceId,我将在customFormat方法上提取它,然后返回customFormat消息,这是我不能开玩笑地介绍的两行内容。

    //index.js
    
    const app = require('express')();
    const cors = require('cors')
    const morgan = require('morgan') // HTTP request logger middleware 
    const logger = require('./lib/logger')(module) //Logger
    const uuid = require('uuid')
    const httpContext = require('express-http-context')
    const config = require('./config').get(process.env.APP_ENV)
    
    
    // Use any third party middleware that does not need access to the context here
    // app.use(some3rdParty.middleware);
    
    app.use(httpContext.middleware);
    // all code from here on has access to the same context for each request
    
    // Run the context for each request.
    // Assigning a unique identifier to each request
    app.use((req, res, next) => {
      httpContext.set('traceId', uuid.v1());
      next();
    });
    
    // using morgan with winston(logger)
    app.use(morgan('combined', {
      stream: {
        write: (message) => logger[config.logLevel](message)
      }
    }))
    
    app.use(cors());
    
    app.listen(4000, () => {
      console.log('Server running on port 4000');
    });
    
    
    
    // lib/logger/index.js
    
    const appRoot = require('app-root-path');
    const {
      createLogger,
      format,
      transports
    } = require('winston');
    require('winston-daily-rotate-file');
    const {
      combine,
      label,
    } = format;
    const config = require('../../config').get(process.env.APP_ENV);
    const loggerHelper = require('./helpers')
    
    
    
    // Custom settings for each transport
    const options = {
      dailyRotateFile: {
        filename: `${appRoot}/logs/TPS-UI-%DATE%.log`,
        datePattern: 'YYYY-MM-DD',
        prepend: true,
        level: config.logLevel,
        timestamp: new Date(),
        localTime: true,
      }
    }
    
    // Instantiate a Winston Logger with the settings
    const logger = moduleObj => createLogger({
      format: combine(
        label({
          label: loggerHelper.getFileName(moduleObj)
        }),
        format.timestamp(),
        loggerHelper.customFormat()
      ),
      transports: [
        new transports.DailyRotateFile(options.dailyRotateFile),
      ],
      exitOnError: false, // do not exit on handled exceptions
    });
    
    
    module.exports = logger
    
    
    // lib/logger/helpers/index.js
    
    const loggerHelper = require('../../../helper');
    const httpContext = require('express-http-context');
    const {
      format: {
        printf
      }
    } = require('winston')
    
    /**
     * @method checkMessageProp
     * @param {message} can be object if developer defined, else it will be string
     *                  if its a network request (morgan requests)
     * @returns a fixed format how the status code and message should show
     */
    const returnLogMessage = (message) => {
      const {
        statusCode,
        logMsg,
        maskedData
      } = message;
      switch (typeof message) {
        case 'object':
          let statusCodeToBeLogged = statusCode ? statusCode : "Status code not defined",
            logMessageToBeLogged = logMsg ? logMsg : "Log message not defined",
            return `${statusCodeToBeLogged} || ${logMessageToBeLogged}`
        case 'string':
          if (message) {
            const messageSplit = message.split('"');
            let statusCodeToBeLogged = messageSplit[2].trim().split(" ")[0],
              logMessageToBeLogged = messageSplit[1]
            return `${statusCodeToBeLogged} || ${logMessageToBeLogged}`;
          }
          return 'Status Code Not Defined || Log Message Not Defined';
        default:
          return message;
      }
    };
    
    /**
     * @method getFileName
     * @param {moduleObj} the module realted object passed from the require of logger file
     * @returns the file name where the logger was invoked
     */
    const getFileName = (moduleObj) => {
      if (Object.keys(moduleObj).length > 0) {
        const tempFileNameParts = moduleObj.filename.split("/");
        const fileName = tempFileNameParts.slice(Math.max(tempFileNameParts.length - 2, 1)).join('/');
        return fileName;
      }
      return 'Module not passed while requiring the logger';
    };
    
    /**
     * @method customFormat
     * @param {log} the log passed by the developer or based on network requests
     * @returns a customFormat how it should be logged to the log files
     */
    
    const customFormat = () => {
      return printf((log) => {
        const traceId = httpContext.get('traceId');
        return `${new Date(log.timestamp)} || ${log.level.toUpperCase()} || ${log.label} || ${traceId} || ${returnLogMessage(log.message)} `;
      })
    }
    
    
    
    module.exports = {
      returnLogMessage,
      getFileName,
      customFormat
    }
    
    // lib/logger/__test__/logger.test.js
    
    jest.mock('winston');
    
    const logger = require('..');
    const winston = require('winston');
    
    describe('Given the logger method is called', () => {
      let loggerObject;
      const mockModuleObject = {
        filename: 'server/index.js'
      };
      beforeEach(() => {
        loggerObject = logger(mockModuleObject);
      });
      test('it should return a object returned by createLogger', () => {
        expect(loggerObject).toEqual(winston.mockLoggerObject);
      });
      test('it should call combine, format, printf and timestamp method of winston', () => {
        expect(winston.mockPrintf).toHaveBeenCalled();
        expect(winston.mockLabel).toHaveBeenCalled();
        expect(winston.mockCombine).toHaveBeenCalled();
      });
      test('expect Dailytransports to be called', () => {
        // how to check the daily transport has been called
        expect(winston.mockDailyTransport).toHaveBeenCalled();
      });
    });
    
    
    // lib/logger/__test__/helpers/helper.test.js
    
    jest.mock('winston')
    jest.mock('express-http-context')
    const helper = require('../../helpers/')
    const httpContext = require('express-http-context')
    const {
      format: {
        printf
      }
    } = jest.requireActual('winston')
    
    describe('Given the helper methods for logger should call a d take different scenarios', () => {
      let mockMessageObj, mockMessageString, mockMessageStringEmpty, mockMessageNumber;
      beforeAll(() => {
        mockMessageObj = {
          statusCode: 200,
          logMsg: "Testing log"
        }
        mockMessageString = `::1 - - [31/Jan/2019:11:26:54 +0000] 
                "GET /graphql HTTP/1.1" 404 146 "-" "Mozilla/5.0 (X11; Linux x86_64)
                 AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36"`
        mockMessageStringEmpty = ""
        mockMessageNumber = 12345
      })
      test('returnLogMessage and getFileName methods should exist', () => {
        expect(helper.returnLogMessage).toBeDefined()
        expect(helper.getFileName).toBeDefined()
      })
      // returnLogMessage Method Test Cases
      test('should return a string when passes object', () => {
        expect(helper.returnLogMessage(mockMessageObj)).toEqual('200 || Testing log || userId : tne:1***:***2354')
      })
      test('should return a string when passes string', () => {
        expect(helper.returnLogMessage(mockMessageString)).toEqual('404 || GET /graphql HTTP/1.1')
      })
      test('should return default string when passes string as undefined', () => {
        expect(helper.returnLogMessage(mockMessageStringEmpty)).toEqual('Status Code Not Defined || Log Message Not Defined || Mask Data Not Defined')
      })
      test('should return the actual default message if the type is nor object neither string', () => {
        expect(helper.returnLogMessage(mockMessageNumber)).toEqual(12345)
      })
      test('should return default message for status code', () => {
        let tempMockMessageObjStatusCode = { ...mockMessageObj
        }
        tempMockMessageObjStatusCode["statusCode"] = ""
        expect(helper.returnLogMessage(tempMockMessageObjStatusCode)).toEqual('Status code not defined || Testing log || userId : tne:1***:***2354')
      })
      test('should return default message for log msg', () => {
        let tempMockMessageObjLogMsg = { ...mockMessageObj
        }
        tempMockMessageObjLogMsg["logMsg"] = ""
        expect(helper.returnLogMessage(tempMockMessageObjLogMsg)).toEqual('200 || Log message not defined || userId : tne:1***:***2354')
      })
      test('should return default message for masked data for undefined', () => {
        let tempMockMessageObjMaskData = { ...mockMessageObj
        }
        tempMockMessageObjMaskData["maskedData"] = ""
        expect(helper.returnLogMessage(tempMockMessageObjMaskData)).toEqual('200 || Testing log || Masked data not defined')
      })
      test('should return default message for masked data for empty object', () => {
        let tempMockMessageObjMaskData = { ...mockMessageObj
        }
        tempMockMessageObjMaskData["maskedData"] = {}
        expect(helper.returnLogMessage(tempMockMessageObjMaskData)).toEqual('200 || Testing log || Masked data not defined')
      })
      // getFileName Method Test Cases
      test('should return default label when module is not passed', () => {
        expect(helper.getFileName({})).toEqual('Module not passed while requiring the logger')
      })
    })
    
    // this one how can i test the custom format method
    describe('custom format', () => {
      test('should call the printF function inside customFormat function', () => {
        
      })
    })
    
    
    // __mocks/winston.js
    const winston = jest.genMockFromModule('winston');
    
    
    const mockLoggerObject = {
      error: jest.fn(),
      info: jest.fn(),
    };
    
    const mockLabel = jest.fn();
    const mocktimestamp = jest.fn();
    const mockPrintf = jest.fn();
    const mockCombine = jest.fn();
    const mockDailyTransport = jest.fn();
    const mockTransports = {
      DailyRotateFile: mockDailyTransport
    };
    const mockCreateLogger = jest.fn().mockReturnValue(mockLoggerObject);
    const mockFormat = {
      label: mockLabel,
      timestamp: mocktimestamp,
      printf: mockPrintf,
      combine: mockCombine,
    };
    
    
    winston.createLogger = mockCreateLogger;
    winston.transports = mockTransports;
    winston.mockLoggerObject = mockLoggerObject;
    winston.format = mockFormat;
    winston.mockLabel = mockLabel;
    winston.mocktimestamp = mocktimestamp;
    winston.mockPrintf = mockPrintf;
    winston.mockDailyTransport = mockDailyTransport;
    winston.mockCombine = mockCombine;
    
    
    module.exports = winston;
    
    
    //__mocks/express-http-context
    
    const httpContext = jest.genMockFromModule('express-http-context');
    
    const mockGet = jest.fn();
    
    httpContext.get = mockGet;
    
    module.exports = httpContext;

    我无法检查的两个测试用例是或抛出错误是一个

    //helper.test.js

    describe('custom format', () => {
        test('should call the printF function inside customFormat function', () => {
            // how can i coverage the line what should be written here
        })
    })
    

    //logger.test.js

    test('expect Dailytransports to be called', () => {
        expect(winston.mockDailyTransport).toHaveBeenCalled(); 
      });
    
    0 回复  |  直到 6 年前