【问题标题】:How to access Winston output from within Jest test如何从 Jest 测试中访问 Winston 输出
【发布时间】:2020-11-01 20:25:57
【问题描述】:

更新:我相信我已经找到了解决这两个问题的(可能是 hacky)方法,明天将发布我更新的代码和解决方案,供任何感兴趣的人使用。

我正在尝试创建 CLI 应用程序并决定格式化日志输出以获得更好的用户体验,包括添加详细模式。因此,我安装了 winston 并设法让它按我的意愿工作。然后我安装了 jest,因为我的应用程序变得越来越复杂,所以我想自动化测试。我决定对记录器进行第一次测试,因为这是我做的最后一件事,我立即遇到了问题。我的计划是计算控制台日志的输出行数并检查它们是否等于 10:这意味着我不必硬编码任何特定于错误的错误消息,如果我决定更改日志格式,这些错误消息可能会改变以后的日期。我还想要一个将环境变量从 dev 更改为 prod 的测试,以确保它在两种环境中都能正常工作:有人知道这是否可能吗?我目前正在使用 dotenv 来管理我的环境变量,并将相关代码添加到我的 jest 配置文件中,以便它可以正确读取变量。我一直在阅读各种堆栈溢出帖子和关于模拟函数的笑话文档,试图解决这些问题,但这一切都在我脑海中浮现。我并不严格需要这些测试才能继续使用我的应用程序,因为我非常有信心它一切正常,但无法解决这些问题令人沮丧,将来知道它会很有用情况下,我确实需要进行依赖于日志输出的测试。有人可以帮忙吗?

set-log-to.js

const { createLogger, format, transports } = require('winston');
const { combine, printf, errors } = format;

const nodeEnvironment = process.env.NODE_ENV;

const cc = require('../constants/chalk-classes');
const {
  fatal,
  caveat,
  victory,
  error,
  warn,
  attempt,
  success,
  info,
  debug,
  plain,
} = cc;

const config = {
  levels: {
    fatal: 0,
    caveat: 0,
    victory: 0,
    error: 1,
    warn: 2,
    attempt: 3,
    success: 3,
    info: 3,
    verbose: 4,
    debug: 5,
  },
};

const formatting = combine(
  errors({ stack: true }),
  printf((infoObj) => {
    const { level, message } = infoObj;
    switch (level) {
      case 'fatal':
        return `${fatal(`${level}:`)} ${plain(message)}`;

      case 'caveat':
        return `${caveat(`${level}:`)} ${plain(message)}`;

      case 'victory':
        return `${victory(`${level}:`)} ${plain(message)}`;

      case 'error':
        return `${error(`${level}: ${message}`)}`;

      case 'warn':
        return `${warn(`${level}: ${message}`)}`;

      case 'attempt':
        return `${attempt(message)}`;

      case 'success':
        return `${success(message)}`;

      case 'info':
        return `${info(message)}`;

      case 'verbose':
        return `${plain(message)}`;

      case 'debug':
        return `${debug(level)}: ${plain(message)}`;
    }
  })
);

function setLevel(level) {
  if (!level) {
    if (nodeEnvironment === 'dev') {
      return (level = 'debug');
    } else {
      return (level = 'warn');
    }
  } else {
    return level;
  }
}

function setLogTo(level) {
  level = setLevel(level);
  const log = createLogger({
    levels: config.levels,
    level,
    transports: [
      new transports.Console({
        format: formatting,
      }),
    ],
  });
  return log;
}

module.exports = setLogTo;

set-log-to.test.js

const setLogTo = require('../set-log-to');

test('All log levels function correctly', () => {
  let log = setLogTo('debug');
  log.fatal('This is fatal');
  log.caveat('This is a caveat');
  log.victory('This is a victory');
  log.error('This is an error');
  log.warn('This is a warning');
  log.attempt('This is an attempt');
  log.success('This is a success');
  log.info('This is some info');
  log.verbose('This is verbose');
  log.debug('This is a debug');
  expect(???).toEqual(10);
});

test('Logger does not print debug as standard', () => {
  let log = setLogTo();
  log.warn('This is a warning');
  log.verbose('This is a verbose statement');
  log.debug('This is a debug statement');
  expect(???).toEqual(1);
});

test('Logger does not print info when set to error', () => {
  let log = setLogTo('error');
  log.info('This is an info statement');
  log.error('This is an error')
  expect(???).toEqual(1);
});

test('Dotenv works correctly', () => {
  let log = setLogTo();
  log.debug('This is a debug');
  nodeEnvironment = 'prod';
  log.debug('This us a debug');
  expect(???).toEqual(1)
});

【问题讨论】:

    标签: javascript node.js logging jestjs winston


    【解决方案1】:

    好的,所以 Winston 不会向控制台发送消息,或者如果发送消息,它就是全局控制台。无论哪种方式,我认为解决此问题的最佳方法是将文件记录器添加到控制台记录器,随后从文件而不是控制台读取行,然后在测试后删除文件。这导致了问题,因为标准的 Jest 行为是同时运行它的测试,所以每个测试都试图同时访问同一个临时文件。这很容易通过在我的 package.json 文件中将 --runInBand 选项传递给 jest 来解决,这使得 Jest 按顺序运行测试。

    为了解决环境变量问题,原来 Jest 在启动时将 NODE_ENV 变量设置为“test”。我在 beforeEach 语句中添加了一个 jest.resetModules() ,允许我在每次测试开始时手动声明环境。我将 jest 测试环境存储在文件开头的常量中,因此我可以将它用于大多数不需要设置显式环境变量的测试。我还创建了一个 afterAll() 函数,以确保将 process.env 设置回测试环境以进行任何后续测试。

    代码:

    package.json:

    "logtest": "jest --runInBand ./src/helper/tests/set-log-to.test.js",

    set-log-to.test.js:

    const fs = require('fs');
    const path = require('path');
    const winston = require('winston');
    
    let setLogTo = require('../set-log-to');
    
    const { snipconDir } = require('../../constants/core-dirs');
    const logTests = path.join(snipconDir, './temp/log-tests.txt');
    const testEnv = process.env;
    
    function createTestLog(level) {
      let log = setLogTo(level);
      log.add(
        new winston.transports.File({
          filename: logTests,
        })
      );
      return log;
    }
    
    function allLogLevels(log) {
      log.fatal('This is fatal');
      log.caveat('This is a caveat');
      log.victory('This is a victory');
      log.error('This is an error');
      log.warn('This is a warning');
      log.attempt('This is an attempt');
      log.success('This is a success');
      log.info('This is some info');
      log.verbose('This is verbose');
      log.debug('This is a debug');
    }
    
    async function getLines() {
      let data = await fs.promises.readFile(logTests);
      let lines = data.toString().split('\n');
      lines.pop();
      return lines;
    }
    
    beforeEach(async (done) => {
      jest.resetModules();
      try {
        await fs.promises.writeFile(logTests, '');
        done();
      } catch (err) {
        console.log(err);
      }
    });
    
    afterEach(async (done) => {
      try {
        await fs.promises.unlink(logTests);
        done();
      } catch (err) {
        console.error('Failed to delete log-tests.js');
      }
    });
    
    afterAll(() => {
      process.env = testEnv;
    });
    
    test('When level is explicitly set to debug, all messages show', async () => {
      process.env = testEnv;
      let log = createTestLog('debug');
      allLogLevels(log);
      let lines = await getLines();
      expect(lines.length).toEqual(10);
    });
    
    test('Standard behaviour shows messages from level 2 and below', async () => {
      process.env = testEnv;
      let log = createTestLog();
      allLogLevels(log);
      let lines = await getLines();
      expect(lines.length).toEqual(5);
    });
    
    test('When explicitly set to error level, loggger displays all level 0 messages', async () => {
      process.env = testEnv;
      let log = createTestLog('error');
      allLogLevels(log);
      let lines = await getLines();
      expect(lines.length).toEqual(3);
    });
    
    test('Verbose level displays messages from level 4 and below', async () => {
      process.env = testEnv;
      let log = createTestLog('verbose');
      allLogLevels(log);
      let lines = await getLines();
      expect(lines.length).toEqual(9);
    });
    
    test('Development environment displays all messages', async () => {
      process.env.NODE_ENV = 'dev';
      let log = createTestLog();
      allLogLevels(log);
      let lines = await getLines();
      expect(lines.length).toEqual(10);
    });
    
    test('Production environment displays messages from level 2 and below', async () => {
      process.env.NODE_ENV = 'prod';
      let log = createTestLog();
      allLogLevels(log);
      let lines = await getLines();
      expect(lines.length).toEqual(5);
    });
    

    set-log-to.js:

    const { createLogger, format, transports } = require('winston');
    const { combine, printf, errors } = format;
    
    const cc = require('../constants/chalk-classes');
    const {
      fatal,
      caveat,
      victory,
      error,
      warn,
      attempt,
      success,
      info,
      debug,
      plain,
    } = cc;
    
    const config = {
      levels: {
        fatal: 0,
        caveat: 0,
        victory: 0,
        error: 1,
        warn: 2,
        attempt: 3,
        success: 3,
        info: 3,
        verbose: 4,
        debug: 5,
      },
    };
    
    const formatting = combine(
      errors({ stack: true }),
      printf((infoObj) => {
        const { level, message } = infoObj;
        switch (level) {
          case 'fatal':
            return `${fatal(`${level}:`)} ${plain(message)}`;
    
          case 'caveat':
            return `${caveat(`${level}:`)} ${plain(message)}`;
    
          case 'victory':
            return `${victory(`${level}:`)} ${plain(message)}`;
    
          case 'error':
            return `${error(`${level}: ${message}`)}`;
    
          case 'warn':
            return `${warn(`${level}: ${message}`)}`;
    
          case 'attempt':
            return `${attempt(message)}`;
    
          case 'success':
            return `${success(message)}`;
    
          case 'info':
            return `${info(message)}`;
    
          case 'verbose':
            return `${plain(message)}`;
    
          case 'debug':
            return `${debug(level)}: ${plain(message)}`;
        }
      })
    );
    
    function setLevel(level) {
      if (!level) {
        if (process.env.NODE_ENV === 'dev') {
          return (level = 'debug');
        } else {
          return (level = 'warn');
        }
      } else {
        return level;
      }
    }
    
    function setLogTo(level) {
      level = setLevel(level);
      const log = createLogger({
        levels: config.levels,
        level,
        transports: [
          new transports.Console({
            format: formatting,
          }),
        ],
      });
      return log;
    }
    
    module.exports = setLogTo;
    

    【讨论】:

      猜你喜欢
      • 2019-06-29
      • 1970-01-01
      • 1970-01-01
      • 2022-11-21
      • 1970-01-01
      • 2023-03-30
      • 2017-12-07
      • 2020-04-17
      • 2019-07-18
      相关资源
      最近更新 更多