2017-07-14 140 views
14

正确模拟以下示例的最佳方法是什么?如何在使用jest的同一模块中模拟函数

问题是,在导入时间后,foo保留对原始未解锁bar的引用。

module.js:

export function bar() { 
    return 'bar'; 
} 

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

module.test.js:

import * as module from '../src/module'; 

describe('module',() => { 
    let barSpy; 

    beforeEach(() => { 
     barSpy = jest.spyOn(
      module, 
      'bar' 
     ).mockImplementation(jest.fn()); 
    }); 


    afterEach(() => { 
     barSpy.mockRestore(); 
    }); 

    it('foo',() => { 
     console.log(jest.isMockFunction(module.bar)); // outputs true 

     module.bar.mockReturnValue('fake bar'); 

     console.log(module.bar()); // outputs 'fake bar'; 

     expect(module.foo()).toEqual('I am foo. bar is fake bar'); 
     /** 
     * does not work! we get the following: 
     * 
     * Expected value to equal: 
     * "I am foo. bar is fake bar" 
     * Received: 
     * "I am foo. bar is bar" 
     */ 
    }); 
}); 

谢谢!

编辑:我可以改变:

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

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

,但是这是对P。在我看来丑陋无处不在:/

回答

2

FWIW,我就解决了解决方案是使用dependency injection,通过设置默认参数。

所以我会改变

export function bar() { 
    return 'bar'; 
} 

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

export function bar() { 
    return 'bar'; 
} 

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

这不是我的组件的API重大更改,我可以很容易地通过执行以下操作

覆盖条在我的测试
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'); 
    }); 
}); 

这样做的好处是可以带来稍微好一点的测试代码:)

+0

我通常不喜欢依赖注入的粉丝,因为您允许测试来改变代码的写法。这就是说,这比当前更高级的答案要好得多,这个答案很丑陋 – Sean

9

这个问题似乎与您如何期待解决的酒吧范围有关。

一方面,在module.js中,您导出两个函数(而不是一个拥有这两个函数的对象)。由于模块导出的方式,导出的东西容器的引用是exports,就像您提到的那样。

另一方面,您处理您的导出(您别名module)就像一个持有这些函数的对象并试图替换它的一个函数(函数栏)。

如果仔细看看你的foo实现,你实际上持有一个固定的bar函数的引用。

当你认为你有一个新的,你只是实际的module.test.js

的范围取代了参考副本使FOO实际使用酒吧的另一个版本,你有更换的酒吧功能两种可能性:

  1. 在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'); 
        }); 
    }); 
    
  2. 就像你说的,改写全球exports容器中的全球基准。这不是推荐的方法,因为如果您没有正确地将导出重置为初始状态,您可能会在其他测试中引入奇怪的行为。

1

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

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的支持循环进口,它的运作非常顺利。

0

如果你定义你的出口,你可以引用你的函数作为出口对象的一部分。然后你可以单独覆盖你的mocks中的函数。这是由于导入如何作为参考,而不是副本。

module.js:

exports.bar() => { 
    return 'bar'; 
} 

exports.foo() => { 
    return `I am foo. bar is ${exports.bar()}`; 
} 

module.test.js:

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'); 
    }); 

}) 
相关问题