如何使用 jest 在 NodeJS 中编写全覆盖的测试用例

How to write test case with full coverage in NodeJS using jest

如何在下面的 NodeJS 代码片段中编写测试用例以获得 100% 的覆盖率

function logger(logFile) {
  return new Promise((resolve) => {
    resolve(
      createLogger({
        level: loggerLevel === 'undefined' ? 'info' : loggerLevel,
        format: format.combine(
          format.timestamp({
            format: 'YYYY-MM-DD HH:mm:ss',
          }),
          format.printf((info) => `${info.timestamp} ${info.level}: ${info.message}`),
        ),
        transports: [new transports.File({ filename: path.join(logDir, logFile) })],
      }),
    );
  });
}

单元测试解决方案如下:

logger.ts

import { createLogger, format, transports } from 'winston';
import path from 'path';

console.log('process.env.LOG_LEVEL: ', process.env.LOG_LEVEL);
const loggerLevel = process.env.LOG_LEVEL || 'debug';
const logDir = './log';

export function logger(logFile) {
  return new Promise((resolve) => {
    resolve(
      createLogger({
        level: loggerLevel === 'undefined' ? 'info' : loggerLevel,
        format: format.combine(
          format.timestamp({
            format: 'YYYY-MM-DD HH:mm:ss',
          }),
          format.printf((info) => `${info.timestamp} ${info.level}: ${info.message}`),
        ),
        transports: [new transports.File({ filename: path.join(logDir, logFile) })],
      }),
    );
  });
}

logger.test.ts:

import { Logger } from 'winston';
import { Format, TransformableInfo } from 'logform';

jest.mock('winston', () => {
  const mFormat = {
    combine: jest.fn().mockReturnThis(),
    timestamp: jest.fn().mockReturnThis(),
    printf: jest.fn().mockReturnThis(),
  };
  const mTransports = {
    File: jest.fn(),
  };
  return { createLogger: jest.fn(), format: mFormat, transports: mTransports };
});

describe('59857333', () => {
  afterEach(() => {
    jest.resetModules();
  });
  it.each`
    LOG_LEVEL      | expected
    ${'debug'}     | ${'debug'}
    ${'undefined'} | ${'info'}
    ${''}          | ${'debug'}
  `('should pass with LOG_LEVEL: $LOG_LEVEL', async ({ LOG_LEVEL, expected }) => {
    const ORIGINAL_LOG_LEVEL = process.env.LOG_LEVEL;
    process.env.LOG_LEVEL = LOG_LEVEL;
    const { logger: loggerProvider } = require('./logger');
    const { createLogger, format, transports } = require('winston');
    const logFile = 'access_log.txt';
    let templateFunction!: (info: TransformableInfo) => string;
    (format.printf as jest.MockedFunction<typeof format.printf>).mockImplementationOnce(function(this: Format, func) {
      templateFunction = func;
      return this;
    });
    const mLogger = {} as Logger;
    (createLogger as jest.MockedFunction<typeof createLogger>).mockReturnValue(mLogger);
    const logger = await loggerProvider(logFile);
    expect(logger).toBeDefined();
    expect(createLogger).toBeCalledWith({
      level: expected,
      format: expect.any(Object),
      transports: [expect.any(Object)],
    });
    expect(format.combine).toBeCalledTimes(1);
    expect(format.timestamp).toBeCalledWith({ format: 'YYYY-MM-DD HH:mm:ss' });
    expect(format.printf).toBeCalledWith(templateFunction);
    expect(transports.File).toBeCalledWith({ filename: 'log/access_log.txt' });

    // test templateFunction
    const info = { timestamp: 666, level: expected, message: 'connection success' };
    const logEntry = templateFunction(info);
    expect(logEntry).toBe(`666 ${expected}: connection success`);

    process.env.LOG_LEVEL = ORIGINAL_LOG_LEVEL;
  });
});

100% 覆盖率的单元测试结果:

 PASS  src/Whosebug/59857333/logger.test.ts (12.615s)
  59857333
    ✓ should pass with LOG_LEVEL: debug (817ms)
    ✓ should pass with LOG_LEVEL: undefined (9ms)
    ✓ should pass with LOG_LEVEL:  (26ms)

  console.log src/Whosebug/59857333/logger.ts:486
    process.env.LOG_LEVEL:  debug

  console.log src/Whosebug/59857333/logger.ts:486
    process.env.LOG_LEVEL:  undefined

  console.log src/Whosebug/59857333/logger.ts:486
    process.env.LOG_LEVEL:  

-----------|----------|----------|----------|----------|-------------------|
File       |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
-----------|----------|----------|----------|----------|-------------------|
All files  |      100 |      100 |      100 |      100 |                   |
 logger.ts |      100 |      100 |      100 |      100 |                   |
-----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        14.8s

源代码:https://github.com/mrdulin/jest-codelab/tree/master/src/Whosebug/59857333