首页 > 解决方案 > 如何使用 jest 模拟返回函数的函数

问题描述

我正在尝试模拟返回另一个函数的函数。但是,我不确定如何使用 jest 来完成这项工作。提前致谢。

  1. 应该成功返回。
  2. 应该抛出一个错误。

需要模拟的模块:


const initGreeter = () =>{
    return {
        sayHello: (name:string) =>{
            console.log(`Hello: ${name}`)
        }
    }
}
export default initGreeter;

被测模块:

import initGreeter from './greeter';

export const greetSomeone = (name:string) =>{
    const greeter = initGreeter();
    greeter.sayHello(name)
}

测试:

import initGreeter from '../../utils/greeter';
import { greetSomeone } from '../../utils/greeterConsumer';

describe('greeterConsumer', () => {
  afterEach(() => {
    jest.clearAllMocks();
  });

  it('greeter consumer should call SayHello on greeter', () => {
    //TODO:mock greeter here
    greetSomeone('sam');
    expect(greeter.sayHello.mock.call[0][0]).toBe('sam');
  });

  it('greeter consumer throws exception', () => {
    //TODO:mock greeter here so it throws exception
    expect(greetSomeone('sam')).toThrow(Error);
  });
});

请注意,更新 1 和 2 中的代码是 @slideshowp2 解决方案中代码的延续。

更新 2:看起来如果更改下面的代码

jest.mock('./greeter', () => {
  return jest.fn(() => mGreeter);
});

jest.mock('../../utils/greeter.ts', () => ({  
  __esModule: true,
  default: jest.fn(() => mGreeter)

}));

有用。

还有其他方法可以使 @slideshowp2 中的代码按原样工作,例如在 tsconfig 中设置 "esModuleInterop": true 。但是,我不完全理解为什么它适用于任何一种情况。我可能会在未来检查并在这里更新。

更新 1:从@slideshowp2 实施解决方案后

我在测试中收到以下错误

失败 src/测试/utils/greeterConsumer.test.ts ● greeterConsumer › greeter 消费者应该在 greeter 上调用 SayHello

TypeError: greeter_1.default is not a function

  2 | 
  3 | export const greetSomeone = (name:string) =>{
> 4 |     const greeter = initGreeter();
    |                     ^
  5 |     greeter.sayHello(name)
  6 | }

  at Object.<anonymous>.exports.greetSomeone (src/utils/greeterConsumer.ts:4:21)
  at Object.<anonymous> (src/__tests__/utils/greeterConsumer.test.ts:18:5)

标签: typescriptunit-testingjestjsmocking

解决方案


您可以使用jest.mock(moduleName, factory, options)手动模拟../../utils/greeter模块。

例如

greeter.ts

const initGreeter = () => {
  return {
    sayHello: (name: string) => {
      console.log(`Hello: ${name}`);
    },
  };
};
export default initGreeter;

greeterConsumer.ts

import initGreeter from './greeter';

export const greetSomeone = (name: string) => {
  const greeter = initGreeter();
  greeter.sayHello(name);
};

greeterConsumer.test.ts

import initGreeter from './greeter';
import { greetSomeone } from './greeterConsumer';

const mGreeter = {
  sayHello: jest.fn(),
};

jest.mock('./greeter', () => {
  return jest.fn(() => mGreeter);
});

describe('greeterConsumer', () => {
  afterEach(() => {
    jest.clearAllMocks();
  });

  it('greeter consumer should call SayHello on greeter', () => {
    greetSomeone('sam');
    expect(initGreeter).toBeCalledTimes(1);
    expect(mGreeter.sayHello).toBeCalledWith('sam');
  });

  it('greeter consumer throws exception', () => {
    mGreeter.sayHello.mockImplementationOnce(() => {
      throw new Error('oops');
    });
    expect(() => greetSomeone('sam')).toThrowError('oops');
    expect(initGreeter).toBeCalledTimes(1);
    expect(mGreeter.sayHello).toBeCalledWith('sam');
  });
});

单元测试结果:

 PASS  examples/65281989/greeterConsumer.test.ts
  greeterConsumer
    ✓ greeter consumer should call SayHello on greeter (3 ms)
    ✓ greeter consumer throws exception (10 ms)

--------------------|---------|----------|---------|---------|-------------------
File                | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
--------------------|---------|----------|---------|---------|-------------------
All files           |     100 |      100 |     100 |     100 |                   
 greeterConsumer.ts |     100 |      100 |     100 |     100 |                   
--------------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        4.473 s

源代码:https ://github.com/mrdulin/jest-v26-codelab/tree/main/examples/65281989


推荐阅读