Тестирование функции библиотеки fs с помощью Jest / Typescript - PullRequest
0 голосов
/ 28 сентября 2018

Я пытаюсь протестировать библиотечную функцию, которую я написал (она работает в моем коде), но не могу заставить работать тест с макетом fs.У меня есть ряд функций для работы с ОС, обернутых в функции, поэтому разные части приложения могут использовать одни и те же вызовы.

Я пытался ответить на этот вопрос с издевательством над файловой системой, но, похоже, это не работает для меня.

Ниже приведен краткий пример, демонстрирующий основы моей проблемы:

import * as fs from 'fs';
export function ReadFileContentsSync(PathAndFileName:string):string {
    if (PathAndFileName === undefined || PathAndFileName === null || PathAndFileName.length === 0) {
        throw new Error('Need a Path and File');
    }
    return fs.readFileSync(PathAndFileName).toString();
}

Итак, сейчас я пытаюсь протестировать эту функцию с помощью Jest:

import { ReadFileContentsSync } from "./read-file-contents-sync";
const fs = require('fs');

describe('Return Mock data to test the function', () => {
    it('should return the test data', () => {
        const TestData:string = 'This is sample Test Data';

// Trying to mock the reading of the file to simply use TestData
        fs.readFileSync = jest.fn();                
        fs.readFileSync.mockReturnValue(TestData);

// Does not need to exist due to mock above     
        const ReadData = ReadFileContentsSync('test-path');
        expect(fs.readFileSync).toHaveBeenCalled();
        expect(ReadData).toBe(TestData);
    });
});

Я получаю исключение, что файл не существует, но я ожидал, что фактический вызов fs.readFileSync не был вызван, но макет jest.fn () былиспользуется.

ENOENT: no such file or directory, open 'test-path'

Я не уверен, как это сделать?

Ответы [ 2 ]

0 голосов
/ 01 октября 2018

Так как я упомянул о функционале / OO / и неприязни к шутливому макету, я чувствую, что должен заполнить здесь некоторое объяснение.

Я не против jest.mock() или любой насмешливой библиотеки (такой какsinon).Я использовал их раньше, и они определенно служат своей цели и являются полезным инструментом.Но я считаю, что они по большей части не нужны, и при их использовании есть некоторый компромисс.

Позвольте мне сначала продемонстрировать три способа реализации кода без использования mock.

Первый способ является функциональным, в качестве первого аргумента используется context:

// read-file-contents-sync.ts
import fs from 'fs';
export function ReadFileContentsSync({ fs } = { fs }, PathAndFileName: string): string {
    if (PathAndFileName === undefined || PathAndFileName === null || PathAndFileName.length === 0) {
        throw new Error('Need a Path and File');
    }
    return fs.readFileSync(PathAndFileName).toString();
}

// read-file-contents-sync.spec.ts
import { ReadFileContentsSync } from "./read-file-contents-sync";

describe('Return Mock data to test the function', () => {
    it('should return the test data', () => {
        const TestData:Buffer = new Buffer('This is sample Test Data');

        // Trying to mock the reading of the file to simply use TestData
        const fs = {
            readFileSync: () => TestData
        }

        // Does not need to exist due to mock above     
        const ReadData = ReadFileContentsSync({ fs }, 'test-path');
        expect(ReadData).toBe(TestData.toString());
    });
});

Второй способ - использование OO:

// read-file-contents-sync.ts
import fs from 'fs';
export class FileReader {
    fs = fs
    ReadFileContentsSync(PathAndFileName: string) {
        if (PathAndFileName === undefined || PathAndFileName === null || PathAndFileName.length === 0) {
            throw new Error('Need a Path and File');
        }
        return this.fs.readFileSync(PathAndFileName).toString();
    }
}

// read-file-contents-sync.spec.ts
import { FileReader } from "./read-file-contents-sync";

describe('Return Mock data to test the function', () => {
    it('should return the test data', () => {
        const TestData: Buffer = new Buffer('This is sample Test Data');

        const subject = new FileReader()
        subject.fs = { readFileSync: () => TestData } as any

        // Does not need to exist due to mock above     
        const ReadData = subject.ReadFileContentsSync('test-path');
        expect(ReadData).toBe(TestData.toString());
    });
});

Третий способ использует модифицированныйфункциональный стиль, который требует TypeScript 3.1 (технически вы можете сделать это до 3.1, но это немного более неуклюже с использованием взлома пространства имен):

// read-file-contents-sync.ts
import fs from 'fs';
export function ReadFileContentsSync(PathAndFileName: string): string {
    if (PathAndFileName === undefined || PathAndFileName === null || PathAndFileName.length === 0) {
        throw new Error('Need a Path and File');
    }
    return ReadFileContentsSync.fs.readFileSync(PathAndFileName).toString();
}
ReadFileContentsSync.fs = fs

// read-file-contents-sync.spec.ts
import { ReadFileContentsSync } from "./read-file-contents-sync";

describe('Return Mock data to test the function', () => {
    it('should return the test data', () => {
        const TestData: Buffer = new Buffer('This is sample Test Data');

        // Trying to mock the reading of the file to simply use TestData
        ReadFileContentsSync.fs = {
            readFileSync: () => TestData
        } as any

        // Does not need to exist due to mock above     
        const ReadData = ReadFileContentsSync('test-path');
        expect(ReadData).toBe(TestData.toString());
    });
});

Первые два способа обеспечивают большую гибкость и изоляцию, потому что каждыйВызов / экземпляр имеют собственную ссылку на зависимость.Это означает, что «макет» одного теста не повлияет на другой.

Третий способ не предотвращает этого, но имеет преимущество в том, что не меняет сигнатуру исходной функции.

Основой всего этого является управление зависимостями.Большую часть времени программа или код трудны в обслуживании, использовании или тестировании, потому что они не обеспечивают способ вызывающего контекста управлять зависимостью вызываемого пользователя.

Полагаться на ложную библиотеку (особенноСистема насмешек, столь же мощная, как jest.mock()), легко может привыкнуть к игнорированию этого важного аспекта.

Одна хорошая статья, которую я рекомендую всем проверить, - Чистая архитектура дяди Боба: https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html

0 голосов
/ 01 октября 2018

Несмотря на то, что комментарии unional указали мне правильное направление, импорт для fs был выполнен в моем коде как import * as fs from 'fs'.Это казалось проблемой.Изменив импорт здесь просто на import fs from 'fs', и это решило проблему.

Поэтому код становится:

import fs from 'fs';
export function ReadFileContentsSync(PathAndFileName:string):string {
    if (PathAndFileName === undefined || PathAndFileName === null || PathAndFileName.length === 0) {
        throw new Error('Need a Path and File');
    }
    return fs.readFileSync(PathAndFileName).toString();
}

И тестовый файл:

jest.mock('fs');
import { ReadFileContentsSync } from "./read-file-contents-sync";

import fs from 'fs';

describe('Return Mock data to test the function', () => {
    it('should return the test data', () => {
        const TestData:Buffer = new Buffer('This is sample Test Data');

// Trying to mock the reading of the file to simply use TestData
        fs.readFileSync = jest.fn();                
        fs.readFileSync.mockReturnValue(TestData);

// Does not need to exist due to mock above     
        const ReadData = ReadFileContentsSync('test-path');
        expect(fs.readFileSync).toHaveBeenCalled();
        expect(ReadData).toBe(TestData.toString());
    });
});
...