Jest - 函数中的模拟回调 fs.writefile

Jest - Mock callback in function fs.writefile

我在这个话题上坚持了 3 个小时。我没有找到如何在这段代码中测试 if(err) 分支的解决方案:

    function createFile(data){

return new Promise(function(resolve, reject) {

    try {

        if(data === null || data === undefined){
            throw new Error(errorMessages.noDataDefined);
        }

        let internalJobId = uuid.v4();
        let fileName = 'project_name' + internalJobId + '.xml';

        fs.writeFile(config.tmpPath + fileName, data, function (err) {
            if (err){
                throw new Error(err.toString());
            } else {
                resolve(fileName);
            }
            
        });

    } catch (error) {
        return reject(error);
    }
});

}

这个测试通过了但是它没有调用 if (err) { throw new Error(err.toString())}

我必须找到一个解决方案,怎么回调returns一个错误,但我没有得到正确的解决方案。

test('Error', () => {

    jest.mock('fs', () => ({
        writeFile: jest.fn((path, data, callback) => callback(Error('some error')))
      }));

    return expect(createFile('Does not matter')).rejects.toThrow('some error');


});

但是这个测试甚至没有拒绝,所以从来没有抛出错误。如果有人能帮助我,我将不胜感激。

这里有两个问题。一是 fs.writeFile 没有被正确模拟。还有一个就是createFile没有正确处理错误,达不到预期。

jest.mock 影响尚未导入并提升到块顶部的模块(或在顶层使用时高于导入)。如果已经导入了使用它的模块,它不会影响 fs。由于 fs 函数通常与其命名空间一起使用,因此它们也可以被模拟为方法。

应该是:

// at top level
import fs from 'fs';
jest.mock('fs', ...);
...

或者:

// inside test
jest.spyOn(fs, 'writeFile').mockImplementation(...);
...

并断言使测试更具体:

expect(fs.writeFile).toBeCalledTimes(1);
expect(fs.writeFile).toBeCalledWith(...);
return expect(createFile('Does not matter'))...

Promise 构造函数不需要 try..catch 因为它已经在其中捕获了所有 同步 错误并且无法从回调中捕获异步错误。对于需要拒绝promise的地方,可以优先选择reject来保证一致性。

fs.writeFile 回调中抛出错误是一个错误,会导致挂起的承诺。它没有机会拒绝承诺,没有机会在回调之外被 try..catch 捕获并导致未捕获的错误。

应该是:

function createFile(data){
    return new Promise(function(resolve, reject) {
        if(data === null || data === undefined){
            reject(new Error(errorMessages.noDataDefined));
        }

        let internalJobId = uuid.v4();
        let fileName = 'project_name' + internalJobId + '.xml';

        fs.writeFile(config.tmpPath + fileName, data, function (err) {
            if (err){
                reject(new Error(err.toString()); // reject(err) ?
            } else {
                resolve(fileName);
            }            
        });
    });
}

为了尽量减少嵌套,不需要promisification的部分可以移到构造函数之外,函数为async:

async function createFile(data){
    if(data === null || data === undefined){
        throw new Error(errorMessages.noDataDefined);
    }

    return new Promise(function(resolve, reject) {    
        let internalJobId = uuid.v4();
        ...

还有fs.promises API可能不需要承诺。

另请注意,new Error(err.toString()) 可能是不必要的,会导致意外的错误消息,从而使断言失败。可以使用 err 原样拒绝承诺。如果目的是去除不必要的错误信息或更改错误堆栈,则应为new Error(err.message).

解决方案是:

 jest.spyOn(fs, 'writeFile').mockImplementation((f, d, callback) => {
                callback('some error');
 });
    

感谢 Estus Flask!