【问题标题】:Jest mock inner functionJest 模拟内部函数
【发布时间】:2025-12-29 22:20:21
【问题描述】:

我有一个名为 helper.js 的文件,其中包含两个函数

export const funcA = (key) => {
   return funcB(key)
};

export const funcB = (key,prop) => {
   return someObj;
};

我有我的 helper.spec.js 来测试 helper.js 文件的功能。

import {funcA,funcB} from 'helper';

describe('helper', () => {
   test('testFuncB', () => {

   }
   test('testFuncA', () => {

   }
}

funcB 的测试非常简单,我只是调用它并期待 someObj
问题是测试funcA,为了测试它我想模拟funcB的响应。

我希望 testFuncB 调用实际的 funcBtestFuncA 调用模拟的 funcB

如何在我的两个测试中实现 funcB 被模拟和原创?

这不是重复的。这是一个不同的情况:他们只模拟内部调用的函数,如果我删除 testFuncB 那么它将是相同的,但我也必须对 testFuncB 执行测试。

【问题讨论】:

标签: javascript jestjs


【解决方案1】:

如果一个 ES6 模块直接导出两个函数(不在类、对象等内,只是直接导出问题中的函数),一个直接调用另一个,那么 不能模拟该调用强>。

在这种情况下,funcB 不能在funcA 内模拟,就像代码当前的编写方式一样。

模拟替换了funcB模块导出,但funcA 不调用funcB模块导出,它只是调用funcB直接。


funcA 中模拟funcB 需要funcAfuncB 调用模块导出

这可以通过以下两种方式之一完成:


funcB 移动到自己的模块中

funcB.js

export const funcB = () => {
  return 'original';
};

helper.js

import { funcB } from './funcB';

export const funcA = () => {
  return funcB();
};

helper.spec.js

import * as funcBModule from './funcB';
import { funcA } from './helper';

describe('helper', () => {

  test('test funcB', () => {
    expect(funcBModule.funcB()).toBe('original');  // Success!
  });

  test('test funcA', () => {
    const spy = jest.spyOn(funcBModule, 'funcB');
    spy.mockReturnValue('mocked');

    expect(funcA()).toBe('mocked');  // Success!

    spy.mockRestore();
  });
});

将模块导入自身

"ES6 modules support cyclic dependencies automatically" 所以将import 一个模块插入自身是完全有效的,这样模块内的函数就可以为模块中的其他函数调用模块导出

helper.js

import * as helper from './helper';

export const funcA = () => {
  return helper.funcB();
};

export const funcB = () => {
  return 'original';
};

helper.spec.js

import * as helper from './helper';

describe('helper', () => {

  test('test funcB', () => {
    expect(helper.funcB()).toBe('original');  // Success!
  });

  test('test funcA', () => {
    const spy = jest.spyOn(helper, 'funcB');
    spy.mockReturnValue('mocked');

    expect(helper.funcA()).toBe('mocked');  // Success!

    spy.mockRestore();
  });
});

【讨论】:

  • 收到(function (exports, require, module, __filename, __dirname) { import 怎么办?
  • @Prakhar 你需要 compile with something like Babel 因为 Node.js 支持 ES6 模块 is still just experimental
  • @Prakhar 在上面的例子中使用exports.funcB(key) 里面的funcA
  • @BrianAdams 我如何只监视嵌套的 1 个特定函数;通过第二种方法?例如:funcA 调用 {funcB, funcC, funcD,} 和 funcC 依次调用 {funcCC} 进而调用 {funcCCC},我想通过模拟 funcCCC 来为 funcA 编写测试
  • @Ganga funcCCC 只需要导出,funcCC 需要通过模块调用。如果funcCC 使用模块调用funcCCC,则可以在测试期间模拟funcCCC 的模块导出。
【解决方案2】:

迟到的答案,但这应该有效。 此外,您应该在自己的文件中测试 funcB,而不是在“助手”测试中。

import { funcB } from './funcB';
import { funcA } from './helper';

jest.mock('./funcB');

describe('helper', () => {
    test('test funcA', () => {
        const funcBSpy = jest.fn();
        funcB.mockImplementation(() => funcBSpy());
        
        funcA();

        expect(funcBSpy).toHaveBeenCalledTimes(1);
    });
});

【讨论】:

    【解决方案3】:
    import * as helper from 'helper';
    
        describe('helper', () => {
           it('should test testFuncA', () => {
              const mockTestFuncB = jest.mock();
              // spy on calls to testFuncB and respond with a mock function
    
               mockTestFuncB.spyOn(helper, 'testFuncB').mockReturnValue(/*your expected return value*/);
    
              // test logic
    
              // Restore helper.testFuncB to it's original function
              helper.testFuncB.mockRestore();
           }
        }
    

    【讨论】:

      【解决方案4】:

      我创建了一种命名空间来处理这个问题:

      let helper = {}
      
      const funcA = (key) => {
         return helper.funcB(key)
      };
      
      const funcB = (key,prop) => {
          return someObj;
      };
      
      helper = { funcA, funcB }
      
      module.exports = helper
      

      然后使用 jest.fn 进行模拟很明显

      【讨论】:

        【解决方案5】:

        你可以使用babel-plugin-rewire提供的__set__函数来模拟内部函数。

        假设你已经设置了 babel-plugin-rewire。

        helper.spec.js

        import {funcA, __set__} as helper from './helper';
        
        describe('helper', () => {
          test('test funcA', () => {
            __set__('funcB', () => {
              return 'funcB return value'
            })
        
            expect(funcA()).toBe('funcB return value'); 
          });
        });
        

        此解决方案的一个优点是您无需更改任何原始代码

        【讨论】:

          【解决方案6】:

          您可以在测试funcA时执行以下技巧:

          1.模拟funcB:

          helper.funcB = jest.fn().mockImplementationOnce(() => <your data>);
          

          2.将funcB(key)改为this.funcB(key)

          我遇到了同样的问题并且工作了!完整代码:

          export const funcA = (key) => {
              return this.funcB(key)
          };
          
          export const funcB = (key,prop) => {
              return someObj;
          };
          

          测试代码:

          import helper from 'helper';
          
          describe('helper', () => {
             test('testFuncB', () => {
                 ...
             }
             test('testFuncA', () => {
                 helper.funcB = jest.fn().mockImplementationOnce(() => <your data>);
             }
          }
          

          【讨论】:

            【解决方案7】:

            我能够得到这个工作。像其他解决方案一样,我将助手和主要逻辑分成两个文件。在测试文件中,我不得不模拟整个帮助文件。

            const { doAdd } = require('./addHelper');
            
            function add(a, b) {
              return doAdd(a, b);
            }
            
            jest.mock('./addHelper');
            
            // ...
            
            it('should call doAdd', () => {
              // hook into doAdd helper method and intercept its return value
              jest.spyOn(helperModule, 'doAdd').mockReturnValue(11);
            
              expect(addModule.add()).toEqual(11);
              expect(helperModule.doAdd).toBeCalled();
            });
            

            这是我的解决方案:

            https://github.com/davidholyko/jest-sandbox

            【讨论】:

              【解决方案8】:

              我认为这可能有效

              import * as helper from 'helper';
              
              describe('helper', () => {
                 test('testFuncB', () => {
              
                 }
                 test('testFuncA', () => {
                    const mockTestFuncB = jest.mock();
                    // spy on calls to testFuncB and respond with a mock function
                    jest.spyOn(helper, 'testFuncB').mockImplementationOnce(mockTestFuncB);
              
                    // Do the testing ...
              
                    // Restore helper.testFuncB to it's original function
                    helper.testFuncB.mockRestore();
                 }
              }
              

              【讨论】:

              • 这个问题的新观众注意:这不起作用。有关解释和工作示例,请参阅 this answer