如何正确地模拟fs/promise并确保在Jest中调用函数?

7cjasjjr  于 2023-10-14  发布在  Jest
关注(0)|答案(1)|浏览(151)

我正在尝试使用Jest为使用fs/promises的模块编写TypeScript单元测试。我试图模拟readdir函数,但我遇到了一些问题,我模拟的函数没有按预期调用。
下面是我的测试设置的简化版本:

// Mock setup
jest.mock('fs/promises', () => ({
  readdir: jest.fn(),
  readFile: jest.fn(),
}));

// Relevant imports
import { calculateMetrics } from '../tradeEvaluator';
import * as fs from 'fs/promises';

// Test block
describe('calculateMetrics', () => {
  const readdirMock = fs.readdir as jest.MockedFunction<typeof fs.readdir>;
  const readFileMock = fs.readFile as jest.MockedFunction<typeof fs.readFile>;

  beforeEach(() => {
    readdirMock.mockResolvedValue([/*... mocked data ...*/]);
  });

  it('should skip invalid JSON files', async () => {
    await calculateMetrics('./data');
    expect(readdirMock).toHaveBeenCalledTimes(1);
    // other assertions...
  });
});

当我运行我的测试时,我得到一个错误,指示readdir没有被调用,即使我的calculateMetrics函数应该调用它。
我得到这个错误:

>>>>   expect(jest.fn()).toHaveBeenCalledTimes(expected)

    Expected number of calls: 1
    Received number of calls: 0

是否有我可能遗漏的特定步骤或我需要确保使其工作的模拟和导入的顺序?

$ node --version 
v20.5.0

$ tsc --version 
Version 5.1.6
// tradeEvaluator.ts
import { promises as fs } from 'fs';
import { join } from 'path';
import { createLogger, format, transports } from 'winston';

const args = process.argv.slice(2);
let optionalTicker = null;

// If a ticker symbol is provided, capture it
if (args.length > 0) {
    optionalTicker = args[0];
}

export const logger = createLogger({
  level: 'info',
  format: format.combine(
    format.timestamp(),
    format.printf(({ timestamp, level, message }) => {
      return `${timestamp} [${level}]: ${message}`;
    })
  ),
  transports: [
    new transports.File({ filename: 'application.log' })
    // new transports.Console()  // if you still want to see the log in the console
  ]
});

export async function calculateMetrics(directory: string, tickerSymbol: string | null = null): Promise<void> {
  let files;
  try {
    files = await fs.readdir(directory);
  } catch(e) {
    console.error(`Error reading directory ${directory}:`, e);
    return;
  }
  if (!files || !files.length) {
    console.error(`No files found in directory ${directory}`);
    return;
  }
.
.
.

组态:
tsconfig.build.json

{
  "compilerOptions": {
    "target": "es2022",
    "module": "commonjs",
    "moduleResolution": "node",
    "outDir": "./build",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules", "**/*.test.ts"]
}
{
  "compilerOptions": {
    "outDir": "./build",
    "module": "commonjs",
    "target": "es6",
    "strict": true
  },
  "include": [
    "src/**/*.ts"
  ],  
  "exclude": [
    "node_modules/alphalytics-data/src/**/*.ts"
  ]
}

tsconfig.json

{
  "compilerOptions": {
    "outDir": "./build",
    "module": "commonjs",
    "target": "es6",
    "strict": true
  },
  "include": [
    "src/**/*.ts"
  ],  
  "exclude": [
    "node_modules/alphalytics-data/src/**/*.ts"
  ]
}

jest.config.js

module.exports = {
    preset: 'ts-jest',
    testEnvironment: 'node',
    roots: ['./src/'],
    transform: {
      '^.+\\.tsx?$': 'ts-jest'
    },
    testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$',
    moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node']
  };
j8yoct9x

j8yoct9x1#

你模仿了fs/promises模块,所以你应该使用import * as fs from 'fs/promises';而不是import { promises as fs } from 'fs';
fs模块没有被模拟,因此fs.readdir将是真实的实现。
例如
tradeEvaluator.ts

import * as fs from 'fs/promises';
// import { promises as fs } from 'fs';
console.log(fs.readdir);

export async function calculateMetrics(directory: string, tickerSymbol: string | null = null): Promise<void> {
    await fs.readdir(directory);
}

tradeEvaluator.test.ts

import { calculateMetrics } from './tradeEvaluator';
import * as fs from 'fs/promises';

jest.mock('fs/promises');

describe('calculateMetrics', () => {
    const readdirMock = fs.readdir as jest.MockedFunction<typeof fs.readdir>;

    beforeEach(() => {
        readdirMock.mockResolvedValue([])
    });

    it('should skip invalid JSON files', async () => {
        await calculateMetrics('./data');
        expect(readdirMock).toHaveBeenCalledTimes(1);
    });
});

试验结果:

PASS  stackoverflow/77273178/tradeEvaluator.test.ts (6.105 s)
  calculateMetrics
    ✓ should skip invalid JSON files (2 ms)

  console.log
    [Function: readdir] {
      _isMockFunction: true,
      getMockImplementation: [Function (anonymous)],
      mock: [Getter/Setter],
      mockClear: [Function (anonymous)],
      mockReset: [Function (anonymous)],
      mockRestore: [Function (anonymous)],
      mockReturnValueOnce: [Function (anonymous)],
      mockResolvedValueOnce: [Function (anonymous)],
      mockRejectedValueOnce: [Function (anonymous)],
      mockReturnValue: [Function (anonymous)],
      mockResolvedValue: [Function (anonymous)],
      mockRejectedValue: [Function (anonymous)],
      mockImplementationOnce: [Function (anonymous)],
      mockImplementation: [Function (anonymous)],
      mockReturnThis: [Function (anonymous)],
      mockName: [Function (anonymous)],
      getMockName: [Function (anonymous)],
      constructor: [Function: AsyncFunction] {
        _isMockFunction: true,
        getMockImplementation: [Function (anonymous)],
        mock: [Getter/Setter],
        mockClear: [Function (anonymous)],
        mockReset: [Function (anonymous)],
        mockRestore: [Function (anonymous)],
        mockReturnValueOnce: [Function (anonymous)],
        mockResolvedValueOnce: [Function (anonymous)],
        mockRejectedValueOnce: [Function (anonymous)],
        mockReturnValue: [Function (anonymous)],
        mockResolvedValue: [Function (anonymous)],
        mockRejectedValue: [Function (anonymous)],
        mockImplementationOnce: [Function (anonymous)],
        mockImplementation: [Function (anonymous)],
        mockReturnThis: [Function (anonymous)],
        mockName: [Function (anonymous)],
        getMockName: [Function (anonymous)]
      }
    }

      at Object.<anonymous> (stackoverflow/77273178/tradeEvaluator.ts:3:9)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        6.333 s, estimated 7 s

相关问题