2017-07-14 78 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'); 
    }); 

}) 
相關問題