【问题标题】:In jest, how do I mock an exported function to return a Promise rather than undefined?开玩笑,我如何模拟一个导出的函数来返回一个 Promise 而不是 undefined?
【发布时间】:2021-06-24 02:26:36
【问题描述】:

我正在使用 jest 和 Typescript。我在服务文件中有这个导出的函数...

export async functionprocessData(
  data: MyDataI,
): Promise<
    ...

然后在我通过 npm cli 调用的单独文件 (run-my-process.ts) 中,我有这个

import {processData } from '../services/my.service';
...
         processData(data)
            .then((result) => {

现在我想从 jest 中模拟“processData”函数,所以我尝试了这个

jest.mock('../services/my.service', () => {
  // The mock returned only mocks the generateServerSeed method.
  const actual = jest.requireActual('../services/my.service');

  return {
      ...actual,
     processData: jest.fn().mockReturnValue(Promise.resolve({
        dataInserted: 1,
      }))
    }
});

...

describe('calls the job', function () {


  it('invokes the function', async () => {
    ...

    jest.spyOn(process, 'exit').mockImplementationOnce(() => {
      throw new Error('process.exit() was called.');
    });

    expect(() => {
      require('./run-my-process');
    }).toThrow('process.exit() was called.');

但测试因错误而终止

ERROR [1624482717612] (71310 on localhost): Cannot read property 'then' of undefined

因此,当使用参数调用时,我的函数 processData 似乎以某种方式评估为“未定义”。模拟我的函数以返回 Promise 并让我的 Jest 测试通过的正确方法是什么?

【问题讨论】:

  • 我注意到您将mockReturnValue() 用于processData。您可能想尝试mockResolvedValue(),因为它看起来像是在进行异步测试。
  • 嗨,戴夫,你试过我的code snippet。如果您需要更多,我可以添加更深入的示例

标签: typescript unit-testing mocking ts-jest


【解决方案1】:

一个更简单的方法可能是:

import * as myService from '../services/my.service';

jest.spyOn(myService, 'processData').mockResolvedValue({ dataInserted: 1 });

使用它,您可以绕过您尝试使用 jest.mock() 进行的复杂模拟

【讨论】:

    【解决方案2】:

    尝试使用您可能需要调整的任何数据,一篇不错的文章here

    processData: jest.fn(() => Promise.resolve({ data: {}}))
    

    导出

    export default jest.fn(() => Promise.resolve({ data: {} }));    
    

    【讨论】:

      【解决方案3】:

      您可以将返回承诺的函数传递给jest.fn

      jest.mock('../services/my.service', () => ({
        ...jest.requireActual('../services/my.service'),
        processData: jest.fn(() => Promise.resolve({ dataInserted: 1 }))
      }));
      

      如果您需要能够更改每个测试的解析值,您可以解析外部范围的可修改变量,或者在测试中使用.mockResolvedValue.mockResolvedValueOnce 模拟解析值。

      解析一个可修改的变量:

      const data = {
        dataInserted: 1,
      };
      
      jest.mock('../services/my.service', () => ({
        ...jest.requireActual('../services/my.service'),
        processData: jest.fn(() => Promise.resolve({ data }))
      }));
      
      describe('...', () => {
        beforeEach(() => {
          data.dataInserted = 100;
        });
      
        it('...', () => {
        });
      });
      

      模拟解析的值

      import { processData } from '../services/my.service';
      
      jest.mock('../services/my.service', () => ({
        ...jest.requireActual('../services/my.service'),
        processData: jest.fn()
      }));
      
      describe('...', () => {
        beforeEach(() => {
          (processData as jest.Mock).mockResolvedValueOnce({ dataInserted: 100 });
        });
      
        it('...', () => {
        });
      });
      

      【讨论】:

        【解决方案4】:

        我看到其他一些答案有一些问题,如果模拟函数的类型不好,Typescript 编译器可能会引发错误。

        由于您使用的是 typescript 和 ts-jest,我想建议使用 mocked https://kulshekhar.github.io/ts-jest/docs/guides/test-helpers 的解决方案

        import { mocked } from "ts-jest/utils";
        import { processData } from '../services/my.service';
        jest.mock("../services/my.service");
        const mockedProcessData = mocked(processData, true); // it has the correct type, I like to mock functions outside my tests but you can use mocked directly inside your tests if you prefer.
        
        // inside your tests
        mockedProcessData.mockResolvedValueOnce({
            dataInserted: 1,
        })
        

        【讨论】:

          【解决方案5】:

          我更喜欢避免使用 jest.mock 函数,因为类型和隐含的问题。此外,我有时会遇到意外行为,jest.mock 是一种神奇的东西,它只能在您无法直接访问源代码的极少数情况下使用。

          为了让你的代码简洁明了,你可以使用依赖倒置模式:

          服务代码:

          export const runProcess = (mapper: (data: Data) => Promise<string>) => {
            // some code
          }
          

          单元测试代码:

          it('should work', () => {
            const mapper = jest.fn(() => Promise.resolve('testing'));
          
          
            expect(runProcess(mapper)).toEqual(...);
          
          })
          

          客户端代码:

          import { runProcess } from './run-process';
          import { dataMapper } from './data-mapper';
          
          runProcess(dataMapper)
          

          您还可以将该数据映射器作为默认值传递给您的代码:

          import { dataMapper, DataMapper } from './data-mapper';
          export const runProcess = (mapper: DataMapper = dataMapper) => {
            // some code
          }
          

          那么您不需要在任何地方将映射器传递给您的代码,但在这种情况下,您正在添加一个理想情况下应该测试的依赖项。 最后尝试回答自己:是否可以编写至少一个集成测试?还应该介绍集成测试,因为如果只使用单元测试效率低下,但如果将它们与集成测试结合使用,它们会很棒。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2017-09-11
            • 2022-10-24
            • 2020-04-21
            • 1970-01-01
            • 2021-09-27
            • 1970-01-01
            • 1970-01-01
            • 2022-12-10
            相关资源
            最近更新 更多