Reputation: 17895
I'm still learning mockito and right now I'm learning how to inject mocks.
I have an object under test with a particular method that depends on other objects. Those objects, in turn, depend on other objects. I want to mock certain things and have those mocks be used everywhere during execution--throughout the control flow of the method.
For example assume there are classes like:
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);
}
}
I need to write test code with portions along the lines of:
....
@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);
}
Whenever apple.price() is called, I want my mock apple to be the one used. Is this possible?
EDIT:
Important note...
the class that contains the object I want to mock does have a setter for that object. However, I don't really have a handle to that class at the level I'm testing. So, following the example, although ProductManager has a setter for Apple, I don't have a way of getting the ProductManager from the GroceryStore object.
Upvotes: 4
Views: 9425
Reputation: 15770
The problem is you create objects you depend on by calling new
instead of injecting it. Inject ProductManager
into Shelf
(e.g. in constructor), and inject Shelf
into GroceryStore
. Then in test use mocks. If you want to use @InjectMocks
, you have to inject by setter methods.
By constructor it could look like this:
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);
}
}
Then you can test it mocking all the objects you depend on:
@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.
Upvotes: 2