代码之家  ›  专栏  ›  技术社区  ›  Steven Scott

用Jest/Typescript测试fs库函数

  •  1
  • Steven Scott  · 技术社区  · 6 年前

    我正在尝试测试一个我已经编写的库函数(它在我的代码中工作),但是无法使用fs的mock进行测试。我有一系列用于操作系统的函数包装在函数中,因此应用程序的不同部分可以使用相同的调用。

    我试着跟着 this question

    下面是一个简短的示例来演示我的问题的基本知识:

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

    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()mock。

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

    2 回复  |  直到 6 年前
        1
  •  5
  •   unional    6 年前

    既然我提到了functional/OO/和对jest mock的厌恶,我觉得我应该在这里做一些解释。

    我不反对 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());
        });
    });
    

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

    前两种方法提供了更大的灵活性和隔离性,因为每个调用/实例都有自己的依赖引用。 这意味着一个测试的“模拟”不可能影响另一个测试。

    所有这些的底层是依赖关系管理。

    依靠模仿库(尤其是像 笑话 )很容易养成忽视这一重要方面的习惯。

    有一篇很好的文章我建议大家去看看,那就是Bob叔叔的干净架构: https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html

        2
  •  1
  •   Steven Scott    6 年前

    虽然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());
        });
    });