Javascript 如何使用jest模拟同一模块中的函数

Javascript 如何使用jest模拟同一模块中的函数,javascript,testing,mocking,jestjs,Javascript,Testing,Mocking,Jestjs,正确模拟以下示例的最佳方法是什么 问题是导入时间过后,foo会保留对原始未经修改的bar的引用 module.js: module.test.js: 谢谢 编辑:我可以更改: export function foo () { return `I am foo. bar is ${bar()}`; } 到 但这是p。在我看来,到处都是丑陋的:/这个问题似乎与你期望如何解决酒吧的范围有关 一方面,在module.js中导出两个函数(而不是包含这两个函数的对象)。由于模块的导出方式,对导出内

正确模拟以下示例的最佳方法是什么

问题是导入时间过后,
foo
会保留对原始未经修改的
bar
的引用

module.js:

module.test.js:

谢谢

编辑:我可以更改:

export function foo () {
    return `I am foo. bar is ${bar()}`;
}


但这是p。在我看来,到处都是丑陋的:/

这个问题似乎与你期望如何解决酒吧的范围有关

一方面,在
module.js
中导出两个函数(而不是包含这两个函数的对象)。由于模块的导出方式,对导出内容容器的引用是
exports
,如您所述

另一方面,您处理导出(您将
模块
化名为)时,就像处理一个包含这些函数并试图替换其中一个函数(函数栏)的对象一样

如果仔细观察foo实现,您实际上持有对bar函数的固定引用

当您认为用一个新函数替换了bar函数时,实际上您只是替换了module.test.js范围内的引用副本

要使foo实际使用另一版本的bar,您有两种可能性:

  • 在module.js中,导出一个类或实例,同时包含foo和bar方法:

    Module.js:

    export class MyModule {
      function bar () {
        return 'bar';
      }
    
      function foo () {
        return `I am foo. bar is ${this.bar()}`;
      }
    }
    
    注意在foo方法中使用了这个关键字

    Module.test.js:

    import { MyModule } from '../src/module'
    
    describe('MyModule', () => {
      //System under test :
      const sut:MyModule = new MyModule();
    
      let barSpy;
    
      beforeEach(() => {
          barSpy = jest.spyOn(
              sut,
              'bar'
          ).mockImplementation(jest.fn());
      });
    
    
      afterEach(() => {
          barSpy.mockRestore();
      });
    
      it('foo', () => {
          sut.bar.mockReturnValue('fake bar');
          expect(sut.foo()).toEqual('I am foo. bar is fake bar');
      });
    });
    
  • 正如您所说,在global
    exports
    容器中重写全局引用这不是推荐的方法,因为如果您没有正确地将导出重置为初始状态,则可能会在其他测试中引入奇怪的行为。


  • fwiw,我决定使用的解决方案是通过设置默认参数

    所以我会改变

    export function bar () {
        return 'bar';
    }
    
    export function foo () {
        return `I am foo. bar is ${bar()}`;
    }
    

    这不是对我的组件API的突破性更改,我可以通过执行以下操作轻松地重写测试中的bar

    import { foo, bar } from '../src/module';
    
    describe('module', () => {
        it('foo', () => {
            const dummyBar = jest.fn().mockReturnValue('fake bar');
            expect(foo(dummyBar)).toEqual('I am foo. bar is fake bar');
        });
    });
    

    这也有助于生成稍微好一点的测试代码:)

    另一种解决方案是将模块导入到自己的代码文件中,并使用所有导出实体的导入实例。像这样:

    import * as thisModule from './module';
    
    export function bar () {
        return 'bar';
    }
    
    export function foo () {
        return `I am foo. bar is ${thisModule.bar()}`;
    }
    
    现在模拟
    bar
    非常容易,因为
    foo
    也使用导出的
    bar
    实例:

    import * as module from '../src/module';
    
    describe('module', () => {
        it('foo', () => {
            spyOn(module, 'bar').and.returnValue('fake bar');
            expect(module.foo()).toEqual('I am foo. bar is fake bar');
        });
    });
    

    将模块导入到自己的代码中看起来很奇怪,但由于ES6支持循环导入,因此工作非常顺利。

    如果定义导出,则可以将函数作为导出对象的一部分引用。然后您可以分别覆盖模拟中的函数。这是由于导入如何作为引用而不是副本工作

    module.js:

    module.test.js:


    我也遇到了同样的问题,由于项目的linting标准,在
    导出中定义类或重写引用都不是代码审查可批准的选项,即使linting定义没有阻止。作为一个可行的选择,我偶然发现了一种更干净的方法,至少在外观上是如此。当我在我接触过的另一个项目中发现它时,我注意到它已经出现在我链接的一个类似问题的答案中。这是一个根据链接答案提供的供参考的问题(不使用间谍)调整的片段(我还添加了分号,以删除间谍,因为我不是异教徒):

    从“../module”作为模块导入;
    描述('foo',()=>{
    它('calls bar',()=>{
    const barMock=jest.fn();
    __重新布线(bar,barMock);
    module.foo();
    期望(bar)。已被催缴的时间(1);
    });
    });适合我:

    cat moduleWithFunc.ts
    
    export function funcA() {
     return export.funcB();
    }
    export function funcB() {
     return false;
    }
    
    cat moduleWithFunc.test.ts
    
    import * as module from './moduleWithFunc';
    
    describe('testFunc', () => {
      beforeEach(() => {
        jest.clearAllMocks();
      });
    
      afterEach(() => {
        module.funcB.mockRestore();
      });
    
      it.only('testCase', () => {
        // arrange
        jest.spyOn(module, 'funcB').mockImplementationOnce(jest.fn().mockReturnValue(true));
    
        // act
        const result = module.funcA();
    
        // assert
        expect(result).toEqual(true);
        expect(module.funcB).toHaveBeenCalledTimes(1);
      });
    });
    
    如果您使用Babel(即
    @Babel/parser
    )来处理代码的透明化,1 npm软件包通过在透明时间为您透明地导出
    替换,非常优雅地解决了这一问题。有关更多信息,请参阅



    1注:我写了这个插件

    我通常不喜欢依赖注入,因为您允许测试更改代码的编写方式。这就是说,这比当前投票率较高的答案要好,后者是相当丑陋的ICER测试,但代码很糟糕。更改代码并不是一个好主意,因为您找不到测试代码的方法。作为一名开发人员,当我查看该代码时,它让我思考了100倍,为什么模块中的某个特定方法作为依赖项传递给同一模块中的另一个方法。我喜欢这样,但对我来说,它在生产包中爆炸了。
    导出未定义
    这对我来说很有效,对现有代码的影响最小,而且容易遵循测试。这对我来说也是最简单的路线。非常有用。谢谢。这应该是可以接受的答案。插件可以正常工作&不需要在测试之外重写任何代码。TY谢谢你,如果你在巴贝尔环境中,那么这就是你要寻找的答案。请参阅
    jest
    GH页面上的此问题帖子,这对我不适用。funcB永远不会被调用。
    import { foo, bar } from '../src/module';
    
    describe('module', () => {
        it('foo', () => {
            const dummyBar = jest.fn().mockReturnValue('fake bar');
            expect(foo(dummyBar)).toEqual('I am foo. bar is fake bar');
        });
    });
    
    import * as thisModule from './module';
    
    export function bar () {
        return 'bar';
    }
    
    export function foo () {
        return `I am foo. bar is ${thisModule.bar()}`;
    }
    
    import * as module from '../src/module';
    
    describe('module', () => {
        it('foo', () => {
            spyOn(module, 'bar').and.returnValue('fake bar');
            expect(module.foo()).toEqual('I am foo. bar is fake bar');
        });
    });
    
    exports.bar () => {
        return 'bar';
    }
    
    exports.foo () => {
        return `I am foo. bar is ${exports.bar()}`;
    }
    
    describe('MyModule', () => {
    
      it('foo', () => {
        let module = require('./module')
        module.bar = jest.fn(()=>{return 'fake bar'})
    
        expect(module.foo()).toEqual('I am foo. bar is fake bar');
      });
    
    })
    
    cat moduleWithFunc.ts
    
    export function funcA() {
     return export.funcB();
    }
    export function funcB() {
     return false;
    }
    
    cat moduleWithFunc.test.ts
    
    import * as module from './moduleWithFunc';
    
    describe('testFunc', () => {
      beforeEach(() => {
        jest.clearAllMocks();
      });
    
      afterEach(() => {
        module.funcB.mockRestore();
      });
    
      it.only('testCase', () => {
        // arrange
        jest.spyOn(module, 'funcB').mockImplementationOnce(jest.fn().mockReturnValue(true));
    
        // act
        const result = module.funcA();
    
        // assert
        expect(result).toEqual(true);
        expect(module.funcB).toHaveBeenCalledTimes(1);
      });
    });