Mockito:在整个控制流中注入模拟

问题描述:

我仍在学习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

问题是你通过调用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