15

I want to test a java method that has an enhanced for on it using Mockito. The problem is that when I don't know how to set the expectations for the enhanced for to work. The following code was gotten from an unanswered question in the mockito google group:

import static org.mockito.Mockito.when;
import static org.testng.Assert.assertTrue;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.mockito.Mockito;
import org.testng.annotations.Test;

public class ListTest
{

  @Test
  public void test()
  {
    List<String> mockList = Mockito.mock(List.class);
    Iterator<String> mockIterator = Mockito.mock(Iterator.class);

    when(mockList.iterator()).thenReturn(mockIter);
    when(mockIter.hasNext()).thenReturn(true).thenReturn(false);
    when(mockIter.next()).thenReturn("A");

    boolean flag = false;
    for(String s : mockList) {
        flag = true;
    }
    assertTrue(flag);
  }
} 

The code inside the for loop never gets executed. Setting expectations for an iterator doesn't work, because the java enhanced for doesn't use the list iterator internally. Setting expectations for List.get() method doesn't either since the enhanced for implementation doesn't seem to call the get() method of the list either.

Any help will be much appreciated.

Maciej Kowalski
  • 25,605
  • 12
  • 54
  • 63
HackerGil
  • 1,562
  • 2
  • 11
  • 12

4 Answers4

29

Mocking the iterator works for me. See below code sample:

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.util.Collection;
import java.util.Iterator;

import org.junit.Before;
import org.junit.Test;

public class TestMockedIterator {

    private Collection<String> fruits;
    private Iterator<String> fruitIterator;

    @SuppressWarnings("unchecked")
    @Before
    public void setUp() {
        fruitIterator = mock(Iterator.class);
        when(fruitIterator.hasNext()).thenReturn(true, true, true, false);
            when(fruitIterator.next()).thenReturn("Apple")
            .thenReturn("Banana").thenReturn("Pear");

        fruits = mock(Collection.class);
        when(fruits.iterator()).thenReturn(fruitIterator);
    }

    @Test
    public void test() {
        int iterations = 0;
        for (String fruit : fruits) {
            iterations++;
        }
        assertEquals(3, iterations);
    }
}
hoipolloi
  • 7,984
  • 2
  • 27
  • 28
  • 1
    In your example you know from before the exact number of items hence you can mock the ` hasNext()`, what if you are creating those items in a for loop when you do not know the number of objects in advance? – Alexandru Barbarosie Oct 20 '15 at 09:54
  • Any answer to above question? what if we do not know number of iterations ? – Deepak S Jan 14 '18 at 03:48
  • 1
    If you don't know the number of iterations (which is a bit odd in a unit test) then this technique cannot be applied. – hoipolloi Jan 15 '18 at 21:16
  • When you know the number of iterations and use this method, mind `false` at the end to end the loop. There is no mentioning this in the answer, I missed it and ended up in a never-ending loop. – havryliuk Mar 28 '23 at 09:04
6

Unless I am missing something, you should probably be returning a real list of mocked values. In this case, construct your list of test string in a generator method and simply return that. In more complex cases you can replace the contents of the list with mocked objects.

As a closing point, I can't imagine why you would ever really need to mock an enhanced for loop. The nature of unit tests don't lend themselves well to that level of inspection. It is an interesting question none the less.

Andrew White
  • 52,720
  • 19
  • 113
  • 137
  • Totally. I am aware that it can be tested creating a list of mocks. I was just wondering what's the enhanced for loop doing because it's not either using the iterator or calling the get() method of the list at any time. How does it access the data structure then? Interesting right? – HackerGil Jun 16 '11 at 22:44
  • Also, I tried executing your code. Once I'd renamed the 'mockIterator' variable to 'mockIter' the test passes. – hoipolloi Jun 16 '11 at 22:53
2

Just want to point something out, because I struggled with this all day:

If you want to use the myList.forEach(...) syntax instead of for(:), you have to include (where you set up your mocked list):

doCallRealMethod().when(myMockedList).forEach(anyObject());
OneWholeBurrito
  • 482
  • 4
  • 14
  • 1
    `anyObject()` is deprecated now, you can use: `doCallRealMethod().when(myMockedList).forEach(any());` – uwolfer Sep 08 '17 at 09:56
1

You want to do something like this.

/**
    * THe mock you want to make iterable
    */
   @Mock
   javax.inject.Instance<Integer> myMockedInstanceObject;

   /**
     * Setup the myMockedInstanceObject mock to be iterable when the business logic
     * wants to loop existing instances of the on the iterable....
     */
    private void setupTransportOrderToTransportEquipmentMapperInstancesToBeIteratble() {
        // (a) create a very real iterator object
        final Iterator<Integer> iterator = Arrays
                .asList(Integer.valueOf(1), Integer.valueOf(2)).iterator();

        // (b) make sure your mock when looped over returns a proper iterator       
        Mockito.doAnswer(new Answer<Iterator<Integer>>() {
            @Override
            public Iterator<Integer> answer(InvocationOnMock invocation)
                    throws Throwable {
                return iterator;
            }
        }).when(myMockedInstanceObject).iterator();

    }

The line coments and javadoc should make it clear enough how to mock the behavior of any iterable, regardless of it being a list, a collection a javax.inject.instance or whatever.

99Sono
  • 3,554
  • 27
  • 39