Поддельные `fs.createFileSync` и` fs.unlinkSync` не вызывают - PullRequest
1 голос
/ 30 марта 2020

У меня есть функция, которая делает много вещей, но среди них есть то, что она копирует файл в специальный каталог, что-то делает с ним (вызывает что-то для взаимодействия с этим файлом без использования модуля 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(). Частично это copyFileSync и unlinkFileSync.

Мой тест выглядит примерно так:

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();

  ...
});

Тест не пройден с ошибками, которые не вызваны ни mockCopyFile, ни mockUnlinkFile. Проблема в том, что соответствующие функции вызываются - я прошел тест с помощью отладчика, и они выполняются без проблем. Поэтому проблема должна заключаться в том, что шпионы не присоединяются должным образом.

Я не знаю, как заставить их вызвать. Я пытался сделать import * as fs from 'fs' и fs.copyFileSync() / fs.unlinkFileSync() в тестируемом файле. Я пытался вставить макеты в функцию beforeAll(). Ни одно из решений не помогает. Я высмеиваю несколько других, не относящихся к делу, вызовов методов в том же тесте spe c, и все они работают точно так, как задумано; это не тот, и я не могу понять, почему.


Мой 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"
  ]
}

1 Ответ

3 голосов
/ 01 апреля 2020

Жасмин spyOn Функция пересмотра возвращает объект класса Spy, который не представляет никакого вызова функции, но имеет вспомогательные методы, касающиеся насмешки над функцией. Вы должны позвонить expect напрямую на fs.<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

ОБНОВЛЕНИЕ

Похоже, что для esModuleInterop установлено значение true внутри tsconfig.json. Это означает, что когда вы import * as fs from 'fs' не сохраните ни одного экземпляра объекта fs.

Вы можете установить esModuleInterop на false и ваши тесты будут проходить с toHaveBeenCalled и toHaveBeenCalledWith, но это может нарушить некоторые другие функции вашего проекта. Вы можете прочитать больше о том, что esModuleInterop делает здесь Понимание esModuleInterop в файле tsconfig .

Если вы не хотите устанавливать esModuleInterop в false, то вам нужно импортировать fs как в ES6 Javascript вот так:

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
    • Измените test сценарий внутри package.json на "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"
      ],
    }
    

Теперь ваши тесты должны проходить.

...