Currently using Mockito to Test a method from one of my classes. My class contains a List, and the method takes in an object of the same class. The issue is when I attempt to it
There are some fundamental issues why your example not does work and throws a NullPointerException
.
add()
on the mocked list effectively doesn't do anything. All void methods on mocks are "no-ops" by defaultCollection.iterator()
under the hood. This returns null, because you've not setup mockito to return anything else.Instead, I would not mock the list and instead pass an actual list. Arrays.asList()
is convenient for testing.
@Before
public void setUp(){
MockitoAnnotations.initMocks(this);
shipment = new Shipment(1, Arrays.asList(mockOrder1, mockOrder2));
shipment2 = new Shipment(2, Arrays.asList(mockOrder3));
}
If you're determined to mock a list then you'll have to mock its behaviour, i.e. making the add() actually store something and .iterator() return an iterator. This can be done rather painfully as follows. I've only included this to demonstrate the principle.
@Mock
private List<String> mockedList;
@Before
public void init() {
MockitoAnnotations.initMocks(this);
List<String> realList = new ArrayList<>();
doAnswer(new Answer<String>() {
@Override
public String answer(InvocationOnMock invocation) throws Throwable {
realList.add(invocation.getArgumentAt(0, String.class));
return null;
}
}).when(mockedList).add(any());
when(mockedList.iterator()).thenAnswer(new Answer<Iterator<String>>() {
@Override
public Iterator<String> answer(InvocationOnMock invocation) throws Throwable {
return realList.iterator();
}
});
mockedList.add("bar");
mockedList.add("baz");
}
@Test
public void iterateOverMockedList() {
for (String each : mockedList) {
System.out.println(each);
}
}
As @Adam says: "Iterating over a list using for-each
syntax calls Collection.iterator()
under the hood. This returns null because you've not setup mockito
to return anything else."
So you have to setup mockito in this way;
@Test
public void test_mergeShipments_increasesByOneWhenAShipmentOfOneAddedToAShipmentORderSizeOfTwo(){
//GIVEN
//Mock the iterator
Iterator<Order> stockIteratorMock = mock(Iterator.class);
//WHEN
//In setUp method you put two objs
when(mockShipmentOrder.size()).thenReturn(2);
//Set a mock for iterator
when(mockShipmentOrder.iterator()).thenReturn(iteratorMock);
// Instruct the iteratorMock when stop to return item
when(iteratorMock.hasNext())
.thenReturn(true)
.thenReturn(true)
.thenReturn(false);
// Instruct the iteratorMock what obj return on each call
// You can skip this: mockShipmentOrders.add(mockOrder1);
when(stockIteratorMock.next())
.thenReturn(mockOrder1)
.thenReturn(mockOrder2);
shipment.mergeShipments(shipment);
//THEN
assertEquals(2, shipment.getShipmentOrders().size());
}
This way is verbose, but you are free to modify the behaviour of the array list and also understand how it works under the wood.
You can't add values to the Mocked element. you can remove @Mock from the list of data and use new keyword to initilize it.
private Shipment shipment;
private Shipment shipment2;
@Mock
private Order mockOrder1;
@Mock
private Order mockOrder2;
@Mock
private Order mockOrder3;
private ArrayList<Order> mockShipmentOrders;
private ArrayList<Order> mockShipmentOrders2;
@Before
public void setUp(){
MockitoAnnotations.initMocks(this);
mockShipmentOrders = new ArrayList<>();
mockShipmentOrders2 = new ArrayList<>();
mockShipmentOrders.add(mockOrder1);
mockShipmentOrders.add(mockOrder2);
mockShipmentOrders2.add(mockOrder3);
shipment = new Shipment(1, mockShipmentOrders);
shipment2 = new Shipment(2, mockShipmentOrders2);
}
@Test
public void test_mergeShipments_increasesByOneWhenAShipmentOfOneAddedToAShipmentORderSizeOfTwo(){
System.out.println(shipment);
System.out.println(shipment2);
shipment.mergeShipments(shipment2);
assertEquals(3, shipment.getShipmentOrders().size());
}