模拟的 `fs.createFileSync` 和 `fs.unlinkSync` 没有被调用

Mocked `fs.createFileSync` and `fs.unlinkSync` are not getting called

我有一个函数可以做很多事情,但其中包括将文件复制到一个特殊目录,用它做一些事情(调用一些东西来与该文件交互而不使用 fs模块),然后在完成后删除复制的文件。

import { copyFileSync, unlinkSync } from 'fs';

myOtherFunction(path: string) {
  ...
}

myIOFunction(somePath: string) {
  var copyPath = resolve('otherDir/file2.csv');
  copyFileSync(somePath, copyPath);
  try {
    myOtherFunction(copyPath);
  } finally {
    unlinkFileSync(copyPath);
  }
}

export myFunction() {
  ...
  myIOFunction(resolve('file1.csv));
}

因为只有 myFunction() 被导出(这是唯一应该能够直接交互的东西),我必须通过它对 myOtherFunction()myIOFunction() 进行单元测试。其中一部分是 copyFileSyncunlinkFileSync

我的测试看起来像这样:

import * as fs from 'fs';
import myFunction from './myFile';

...

it("tests something involving input/output", () => {
  mockCopyFile = spyOn(fs, 'copyFileSync');
  mockUnlinkFile = spyOn(fs, 'unlinkSync');

  ...

  myFunction();

  expect(mockCopyFile).toHaveBeenCalledWith(resolve('file1.csv'), resolve('otherDir/file2.csv'));
  expect(mockUnlinkFile).toHaveBeenCalled();

  ...
});

测试失败,错误是 mockCopyFilemockUnlinkFile 都没有被调用。问题是相应的函数 被调用 - 我已经使用调试器逐步完成测试,并且它们执行时没有问题。所以问题一定是间谍没有正确依附自己。

我不知道如何他们被调用。我试过在被测试的文件中做 import * as fs from 'fs'fs.copyFileSync()/fs.unlinkFileSync() 。我试过将模拟放在 beforeAll() 函数中。这两种解决方案都没有帮助。我在同一个测试规范中模拟其他几个不直接相关的方法调用,它们都完全按预期工作;只有这个不是,我不明白为什么。


我的 package.json 包含以下依赖项:

  "scripts": {
    "test": "tsc && jasmine",
  },
"devDependencies": {
    "@types/jasmine": "^3.5.10",
    "@types/node": "^13.7.7",
    "@types/pg": "^7.14.3",
    "copyfiles": "^2.2.0",
    "jasmine": "^3.5.0",
    "jasmine-core": "^3.5.0",
    "jasmine-ts": "^0.3.0",
    "js-yaml": "^3.13.1",
    "mock-fs": "^4.11.0",
    "morgan": "^1.10.0",
    "nodemon": "^2.0.2",
    "swagger-ui-express": "^4.1.3",
    "ts-node": "^8.7.0",
    "typescript": "^3.8.3"
  },
  "dependencies": {
    "@types/express": "^4.17.3",
    "chokidar": "^3.3.1",
    "cors": "^2.8.5",
    "csv-writer": "^1.6.0",
    "dotenv": "^8.2.0",
    "express": "^4.17.1",
    "murmurhash": "0.0.2",
    "pg": "^7.18.2",
    "pg-format": "^1.0.4",
    "winston": "^3.2.1"
  }

我的 jasmine.json 看起来像这样:

{
  "spec_dir": "dist",
  "spec_files": [
    "**/*[sS]pec.js"
  ],
  "helpers": [
    "helpers/**/*.js"
  ],
  "stopSpecOnExpectationFailure": false,
  "random": true
}

tsconfig

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "module": "commonjs",
    "esModuleInterop": true,
    "target": "es6",
    "moduleResolution": "node",
    "sourceMap": true,
    "outDir": "dist",
    "typeRoots": [
      "node_modules/@types",
      "node_modules/@types/node"
    ],
  },
  "lib": [
    "es2015"
  ]
}

Jasmine spyOn mocking function returns a Spy class 不代表任何函数调用的对象,但它有关于模拟函数的辅助方法。您必须直接调用 expectfs.<function> 以检查它是否被调用:

import * as fs from 'fs';
import * as path from 'path';
import { myFunction } from '../src/myFunction';

describe('MyFunc', () => {
  it("tests something involving input/output", () => {
    spyOn(fs, 'copyFileSync');
    spyOn(fs, 'unlinkSync');

    myFunction();

    expect(fs.copyFileSync).toHaveBeenCalledWith(
      path.resolve('file1.csv'),
      path.resolve('otherDir/file2.csv')
    );
    expect(fs.unlinkSync).toHaveBeenCalled();
  });
});

您可以使用此 GitHub 存储库测试一个简单的重现示例:https://github.com/clytras/fs-jasminte-ts-mocking

git clone https://github.com/clytras/fs-jasminte-ts-mocking.git
cd fs-jasminte-ts-mockin
npm i
npm run test

更新

您似乎在 tsconfig.json 中将 esModuleInterop 设置为 true。这意味着当您 import * as fs from 'fs' 不会保留 fs 对象的单个实例时。

您可以将 esModuleInterop 设置为 false 并让您的测试通过 toHaveBeenCalledtoHaveBeenCalledWith,但这可能会破坏项目的某些其他功能。您可以在此处 .

阅读更多关于 esModuleInterop 的内容

如果您不想将 esModuleInterop 设置为 false,那么您必须像在 ES6 Javascript 中那样导入 fs,如下所示:

import fs from 'fs'; // Use plain default import instead of * as
import path from 'path';
import { myFunction } from '../src/myFunction';

describe('MyFunc', () => {
  it("tests something involving input/output", () => {
    spyOn(fs, 'copyFileSync');
    spyOn(fs, 'unlinkSync');

    myFunction();

    expect(fs.copyFileSync).toHaveBeenCalledWith(
      path.resolve('file1.csv'),
      path.resolve('otherDir/file2.csv')
    );
    expect(fs.unlinkSync).toHaveBeenCalled();
  });
});

我还注意到您的配置文件中缺少以下内容:

  1. 你必须使用 jasmine-console-reporter 如果你不这样做:

    • npm i -D jasmine-console-reporter
    • package.json 中的 test 脚本更改为 "test": "tsc && jasmine --reporter=jasmine-console-reporter"
  2. jasmine.json 中,将 ts-node/register/type-check.js 添加到助手,例如:

    {
      ...
      "helpers": [
        "helpers/**/*.js",
        "../node_modules/ts-node/register/type-check.js"
      ],
    }
    

现在你的测试应该通过了。