【问题标题】:Mock of promise based method is not working基于承诺的模拟方法不起作用
【发布时间】:2019-04-30 13:39:51
【问题描述】:

尝试在 JEST 中为我用 typescript 编写的 node.js 编写测试。我要测试的功能非常复杂(我的意思是里面发生了很多事情)。它返回一个 Promise 并且在其中还有 2 个其他函数正在返回一个 Promises (其中一个正在进行异步调用)+ 是 BehaviorSubjectObservables(使用 forEach 从数组中创建)和 forkJoin

我正在尝试模拟 1 个基于 Promise 的嵌套函数的响应。它的结果被分配给一个Observable。我之所以试图改变它的响应是因为我想测试我的主函数。

我面临的问题是,当我创建异步函数的模拟时,对主函数的测试看起来像是忽略了我的模拟并转到原始模拟。

这是我的模块的一个示例(尝试简化它):

import { BehaviorSubject, forkJoin } from 'rxjs'

export const tableConfig: any[] = [{
    tableName: 'TableOne'
},
{
    tableName: 'TableTwo'
},
{
    tableName: 'TableThree'
}];

const exampleApiTableData: any = {
    TableOne: [],
    TableTwo: [],
    TableThree: [],
}

export const pullTableData = (tableName: string): Promise<any[]> => { //async Promise function 1
    return new Promise((resolve, reject) => {
        // Here is a async api call with some more logic but to make it simple and short giving such an example
        setTimeout(() => {
            resolve(exampleApiTableData[tableName]);
        }, 1000);
    })
}

export const buildNewTable = (tableOne: any[], tableTwo: any []): Promise<any[]> => { // Promise function 2
    return new Promise((resolve, reject) => {
        //simplified example
        resolve(tableOne.concat(tableTwo));
    })
}

export const getTables = (): Promise<any> => { // Master
    return new Promise((resolve, reject) => {
        const errors: string[] = [];
        const allTableData$: any[] = [];
        const observableNames: any = {};

        tableConfig.forEach(table => {
            observableNames[table.tableName + 'Source'] = new BehaviorSubject<string[]>([]);
            observableNames[table.tableName] = observableNames[table.tableName + 'Source'].asObservable();
            allTableData$.push(observableNames[table.tableName]);
            pullTableData(table.tableName).then((result: any[]) => {
                observableNames[table.tableName + 'Source'].next(result);
                observableNames[table.tableName + 'Source'].complete();
            }).catch((error: any) => {
                errors.push(error);
                observableNames[table.tableName + 'Source'].next(error);
                observableNames[table.tableName + 'Source'].complete();
            })
        });

        forkJoin(allTableData$).subscribe((results: any) => {
            if (errors.length > 0) reject(errors);
            buildNewTable(observableNames.TableOneSource.value, observableNames.TableTableTwoSource.value).then((result: any[]) => {
                // console.log(result);
                resolve(result);
            }).catch((error: any) => {
                // console.log(error);
                reject(error);
            });
        });
    });
}

这是我创建的测试,但它没有得到 mockRejectedValue 值,而是一直在调用 pullTableData

import * as tableMethods from './index'

describe(`Test the Table methods`, () => {
    test(`it should return and error`, () => {
        const expectedError = `I'm an error`
        jest.fn(tableMethods.pullTableData).mockRejectedValue(expectedError);

        return tableMethods.getTables().then((data: any) => {

        }).catch((error: any) => {
            expect(error).toBe(expectedError);
        })
    })
})

我做错了什么? 有没有办法模拟 pullTableDatabuildNewTable 以便我可以测试 getTable 函数?

【问题讨论】:

    标签: typescript unit-testing jestjs ts-jest


    【解决方案1】:

    您只需要更改几处代码:

    import { BehaviorSubject, forkJoin } from 'rxjs'
    import * as index from './index';  // <= import module into itself
    
    // ...    
    
    export const getTables = (): Promise<any> => { // Master
    
      // ...
    
          index.pullTableData(table.tableName).then((result: any[]) => {  // <= call pullTableData using the module
    
      // ...
    
          if (errors.length > 0) reject(new Error(errors.join(', ')));  // <= reject using an Error object
    
      // ...
    }
    

    ...那么你可以像这样测试它:

    import * as tableMethods from './index'
    
    describe(`Test the Table methods`, () => {
      test(`it should return and error`, async () => {  // <= async test function
        const expectedError = `I'm an error`
        jest.spyOn(tableMethods, 'pullTableData').mockRejectedValue(expectedError);  // <= use jest.spyOn
    
        await expect(tableMethods.getTables()).rejects.toThrow(`I'm an error, I'm an error, I'm an error`);  // Success!
      })
    })
    

    详情

    使用类似jest.spyOn 的东西来模拟一个函数会替换函数的模块导出

    在这种情况下,getTables 直接调用pullTableData,因此模拟pullTableData 的模块导出没有任何效果。

    TypeScript 和 ES6 模块 support cyclic dependencies automatically,因此您可以将模块导入自身,以便为函数调用 模块导出,以便在模拟模块导出时调用模拟函数而不是原件。

    最佳实践是始终使用reject 对象Error

    使用Error 对象拒绝还允许您在测试中使用.rejects.toThrow

    使用rejectsresolves 要求您从expect 返回Promise,或者使用async 测试函数和await Promise

    【讨论】:

    • 谢谢!它正在工作。我使用 return tableMethods.getTables().catch.... 而不是 await expect(tableMethods.getTables()).rejects ...那里)我应该如何模拟它?目前我们正在创建一个 spyOn 并模拟响应 (mockRejectedValue)
    • 可以移到自己的模块中吗?这将使为它创建手动模拟变得容易得多。 @Kosmonaft
    猜你喜欢
    • 2017-02-26
    • 2016-12-16
    • 1970-01-01
    • 2017-04-29
    • 2017-05-08
    • 2015-05-29
    • 1970-01-01
    • 2016-06-07
    • 2021-03-18
    相关资源
    最近更新 更多