2010-10-15 51 views
4

我仍在學習mockito,現在我正在學習如何注入mock。Mockito:在整個控制流中注入模擬

我有一個測試對象與依賴於其他對象的特定方法。這些對象又取決於其他對象。我想嘲笑某些事情,並在執行過程中隨處使用這些模擬 - 貫穿方法的控制流程。

例如,假設有像類:

public class GroceryStore { 
    public double inventoryValue = 0.0; 
    private shelf = new Shelf(5); 
    public void takeInventory() { 
     for(Item item : shelf) { 
      inventoryValue += item.price(); 
     } 
    } 
} 

public class Shelf extends ArrayList<Item> { 
    private ProductManager manager = new ProductManager(); 
    public Shelf(int aisleNumber){ 
     super(manager.getShelfContents(aisleNumber); 
    } 
} 

public class ProductManager { 
    private Apple apple; 
    public void setApple(Apple newApple) { 
     apple = newApple; 
    } 
    public Collection<Item> getShelfContents(int aisleNumber) { 
     return Arrays.asList(apple, apple, apple, apple, apple); 
    } 
} 

我需要編寫測試代碼與沿着線的部分:

.... 
@Mock 
private Apple apple; 
... 
when(apple.price()).thenReturn(10.0); 
... 

... 
@InjectMocks 
private GroceryStore store = new GroceryStore(); 
... 
@Test 
public void testTakeInventory() { 
    store.takeInventory(); 
    assertEquals(50.0, store.inventoryValue); 
} 

每當apple.price()被調用時,我想我的嘲笑蘋果是使用的。這可能嗎?

編輯:
重要提示...
包含我想嘲笑確實有該對象的二傳手對象的類。但是,我在測試的級別上並沒有真正掌握該類。因此,遵循這個例子,儘管ProductManager有一個Apple的setter,但我沒有辦法從GroceryStore對象獲取ProductManager。

+0

我認爲你必須創建蘋果,然後模擬工廠工廠 – 2010-10-15 12:29:19

+0

@Alois:沿着這些線可能是正確的,但。 。 。如何讓ProductManager使用工廠(從GroceryStore的單元測試中)? – gMale 2010-10-15 12:31:51

+0

用ProductManager中的setter來定義工廠。你使用任何DI(依賴注入)框架?例如春天或者guice – 2010-10-15 12:38:10

回答

2

問題是你通過調用new而不是注入來創建你所依賴的對象。將ProductManager注入Shelf(例如在構造函數中),並將Shelf注入GroceryStore。然後在測試中使用mocks。如果你想使用@InjectMocks,你必須通過setter方法注入。

通過構造它可能看起來像這樣:

public class GroceryStore { 
    public double inventoryValue = 0.0; 
    private shelf; 

    public GroceryStore(Shelf shelf) { 
    this.shelf = shelf; 
    } 

    public void takeInventory() { 
    for(Item item : shelf) { 
     inventoryValue += item.price(); 
    } 
    } 
} 

public class Shelf extends ArrayList<Item> { 
    private ProductManager manager; 

    public Shelf(int aisleNumber, ProductManager manager) { 
    super(manager.getShelfContents(aisleNumber); 
    this.manager = manager; 
    } 
} 

public class ProductManager { 
    private Apple apple; 
    public void setApple(Apple newApple) { 
    apple = newApple; 
    } 
    public Collection<Item> getShelfContents(int aisleNumber) { 
    return Arrays.asList(apple, apple, apple, apple, apple); 
    } 
} 

然後你就可以測試它嘲笑你依賴的對象:

@Mock 
private Apple apple; 
... 
when(apple.price()).thenReturn(10.0); 

@InjectMocks 
private ProductManager manager = new ProductManager(); 

private Shelf shelf = new Shelf(5, manager); 
private GroceryStore store = new GroceryStore(shelf); 

//Then you can test your store. 
+0

我忘了我有這個問題打開! :)最基本的答案是「不,你不能在方法調用的控制流中注入模擬。」意思是,你不能創建一個模擬,並自動適用於任何地方。你必須手動「安裝」它在你想要的地方,可以這麼說。要求「在任何你看到蘋果的地方,用我的模擬代替它」會更加方便!但這是不能做到的。你是對的,唯一的解決辦法是改變代碼並注入模擬,「手動」。感謝您花時間回覆。 – gMale 2010-11-03 13:21:47

+0

不客氣。注入依賴關係通常是好的做法,而不是通過「新建」來創建它們。它使你的代碼更可測試。米斯科Hevery有很好的指導:http://misko.hevery.com/code-reviewers-guide/flaw-constructor-does-real-work/ – amorfis 2010-11-03 14:40:44

+0

@gmale:其實,你可以做到這一點(即,模擬所有實例),但它需要更強大的嘲弄工具,如JMockit(我自己的)或PowerMockito。 – 2010-12-08 16:07:56