【问题标题】:How to mock a function with callback parameters in JestJS如何在 JestJS 中模拟带有回调参数的函数
【发布时间】:2020-11-11 15:58:18
【问题描述】:

我的 NodeJS 应用程序有一个函数 readFilesJSON() 调用 fs.readFile(),它当然会调用带有参数 (err,data) 的回调。 Jest 单元测试需要走错误路径和数据路径。

我的解决方案是模拟对fs.readFile() 的调用(见下文)。模拟函数只是根据测试逻辑传递错误或数据。这种方法在只有一个功能被测试时有效。当有多个函数调用fs.readFile() 时,就会出现我看到的问题。 Jest 同时运行所有测试,函数的异步特性意味着对fs.readFile() 的调用没有保证的顺序。这种不确定的行为会破坏错误/数据逻辑和使用toHaveBeenCalledWith() 的参数检查逻辑。

Jest 是否提供一种机制来管理模拟的独立使用?

function readFilesJSON(files,done) {
    let index = 0;
    readNextFile();
    function readNextFile() {
        if( index === files.length ) {
            done();
        }
        else {
            let filename = files[index++];
            fs.readFile( filename, "utf8", (err,data) => {
                if(err) {
                    console.err(`ERROR: unable to read JSON file ${filename}`);
                    setTimeout(readNextFile);
                }
                else {
                    // parse the JSON file here
                    // ...
                    setTimeout(readNextFile);
                }
            });
        }
    }
}

注入的函数设置如下:

jest.spyOn(fs, 'readFile')
    .mockImplementation(mockFsReadFile)
    .mockName("mockFsReadFile");

function mockFsReadFile(filename,encoding,callback) {
    // implement error/data logic here
}

【问题讨论】:

    标签: javascript node.js unit-testing jestjs


    【解决方案1】:

    您可以将不同的场景分隔在不同的describe 块中,并在清除之前对观察函数的调用后调用您的函数,以免得到误报结果。

    
    import { readFile } from "fs";
    
    import fileParser from "./location/of/your/parser/file";
    
    jest.mock("fs");
    
    // mock the file parser as we want to test only readFilesJSON
    jest.mock("./location/of/your/parser/file");
    
    describe("readFilesJSON", () => {
      describe("on successful file read attempt", () => {
        let result;
    
        beforeAll(() => {
          // clear previous calls
          fileParser.mockClear();
          readFile.mockImplementation((_filename, _encoding, cb) => {
            cb(null, mockData);
          });
          result = readFilesJSON(...args);
        });
    
        it("should parse the file contents", () => {
          expect(fileParser).toHaveBeenCalledWith(mockData);
        });
      });
    
      describe("on non-successful file read attempt", () => {
        let result;
    
        beforeAll(() => {
          // clear previous calls
          fileParser.mockClear();
          readFile.mockImplementation((_filename, _encoding, cb) => {
            cb(new Error("something bad happened"), "");
          });
          result = readFilesJSON(...args);
        });
    
        it("should parse the file contents", () => {
          expect(fileParser).not.toHaveBeenCalled();
        });
      });
    });
    
    

    【讨论】:

    • 感谢您的快速回复。这很有趣:每个测试都提供了一个单独的模拟函数实例。让我问你这个问题:如果 Test1 在 Test2 调用 fileParser.mockClear() 时仍在执行,Test1 是否仍然为所有 fileParser 模拟保留其上下文?我对文档的幼稚阅读让我认为在调用 mockFn.mockClear() 后所有的模拟实例都将不复存在。我会试试看。
    • @LeeJenkins Test2 在 Test1 结束之前不会开始。单独的测试文件将并行运行。 here's an example
    • 谢谢!我误解了执行模型。我的测试——都在同一个文件中——以一种破碎的方式并行运行。我希望测试能够开箱即用地处理异步执行。您的回答和评论使我提出了新问题,并且我了解到您必须将测试设置为异步感知。见jestjs.io/docs/en/asynchronous.html
    猜你喜欢
    • 1970-01-01
    • 2014-02-09
    • 2017-12-25
    • 1970-01-01
    • 1970-01-01
    • 2022-07-26
    • 2020-05-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多