使用Jest / Typescript和虚拟函数测试fs库函数

时间:2018-10-09 16:17:54

标签: typescript testing jestjs

在尝试测试使用fs模块的库函数时,在this question中为我提供了帮助,以更好地测试该功能,而没有模拟,我同意@unional是一种更好的方法。

我正在尝试对accessSync方法执行相同的操作,但是它的工作方式不同,需要进行一些更改才能进行测试。

我的代码,遵循@unional建议的更改:

import fs from 'fs';
export function AccessFileSync(PathAndFileName: string):boolean {
    if (PathAndFileName === undefined || PathAndFileName === null || PathAndFileName.length === 0) {
        throw new Error('Missing File Name');
    }
    try {
        AccessFileSync.fs.accessSync(PathAndFileName, fs.constants.F_OK | fs.constants.R_OK);
    } catch {
        return false;
    }
    return true;
}
AccessFileSync.fs = fs;

现在,要尝试对其进行测试,我将:

describe('Return Mock data to test the function', () => {
    it('should return the test data', () => {
        // mock function
        AccessFileSync.fs = {
            accessSync: () => { return true; }
        } as any;

        const AccessAllowed:boolean = AccessFileSync('test-path');      // Does not need to exist due to mock above
        expect(AccessFileSync.fs.accessSync).toHaveBeenCalled();
        expect(AccessAllowed).toBeTruthy();
    });
});

这确实适用于第一个测试,但是后续测试(更改测试)不会获得新值。例如:

describe('Return Mock data to test the function', () => {
    it('should return the test data', () => {
        // mock function
        AccessFileSync.fs = {
            accessSync: () => { return true; }
        } as any;

        const AccessAllowed:boolean = AccessFileSync('test-path');      // Does not need to exist due to mock above
        expect(AccessFileSync.fs.accessSync).toHaveBeenCalled();
        expect(AccessAllowed).toBeTruthy();
    });
});
describe('Return Mock data to test the function', () => {
    it('should return the test data', () => {
        // mock function
        AccessFileSync.fs = {
            accessSync: () => { return false; }
        } as any;

        const AccessAllowed:boolean = AccessFileSync('test-path');      // Does not need to exist due to mock above
        expect(AccessFileSync.fs.accessSync).toHaveBeenCalled();
        expect(AccessAllowed).toBeFalsy();  // <- This Fails
    });
});

此外,我想使用tslint传递,它不喜欢as any布局,并且更喜欢Variable:type表示法。

1 个答案:

答案 0 :(得分:1)

您的代码永远不会返回false

import fs from 'fs';
export function AccessFileSync(PathAndFileName: string): boolean {
  if (PathAndFileName === undefined || PathAndFileName === null || PathAndFileName.length === 0) {
    throw new Error('Missing File Name');
  }
  try {
    AccessFileSync.fs.accessSync(PathAndFileName, fs.constants.F_OK | fs.constants.R_OK);
  }
  catch {
    return false; // here
  }
  return true;
}
AccessFileSync.fs = fs;

此外,您的存根需要抛出以模拟相同的行为。

describe('Return Mock data to test the function', () => {
  it('should return the test data', () => {
    // mock function
    AccessFileSync.fs = {
      accessSync: () => { throw new Error('try to mimic the actual error') }
    };

    const AccessAllowed: boolean = AccessFileSync('test-path');
    expect(AccessAllowed).toBeFalsy(); 
  });
});

对于皮棉错误,有两种处理方法。

第一个是类型断言,它是您要做的,您可以将其强制转换为所需的任何内容,例如as typeof fs,但就我个人而言,我认为这太过分了。

通常不建议使用类型断言,因为它只告诉编译器,“嘿,我知道您以为xX,但我知道它实际上是Y,所以让我们来对待它作为Y”,因此您基本上失去了类型检查的好处。

但是专门针对模拟和存根,这是可以的,因为您有意识地意识到自己在“伪造”它,并且可以进行测试以备份丢失的类型检查。

第二种方法涉及接口隔离原则(ISP,SOLID原则中的I)。

这个想法是要问您真正需要什么,而不是获取整个类型/接口。

AccessFileSync.fs = fs as Pick<typeof fs, 'accessSync'>

您的测试不再需要执行类型声明。

请注意,这里我必须使用类型断言,因为X.y: <type> = value不是有效的语法。

我可以这样做:

const fs2: Pick<typeof fs, 'accessSync'> = fs
AccessFileSync.fs = fs2

但这只是愚蠢的。

这样做的好处是它更精确,并且可以精确跟踪您的实际使用情况。

它的缺点是它比较乏味。我希望控制流分析将来能自动为我做。 :)