首页 > 解决方案 > 基于承诺的模拟方法不起作用

问题描述

尝试在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函数?

标签: typescriptunit-testingjestjsts-jest

解决方案


您只需要更改几处代码:

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 模块自动支持循环依赖,因此您可以将模块导入自身以调用函数的模块导出,以便在模拟模块导出时调用模拟函数而不是原始函数。

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

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

使用rejectsand要求您从resolves中返回或使用测试函数和.PromiseexpectasyncawaitPromise


推荐阅读