Тестирование улучшенного поведения Java с помощью Mockito

Я хочу протестировать метод java, который имеет расширенные возможности, используя Mockito. Проблема в том, что когда я не знаю, как установить ожидания для расширенного для работы. Следующий код был получен из вопроса без ответа в группе mockito google :

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);
  }
} 

Код внутри цикла for никогда не выполняется. Установка ожиданий для итератора не работает, потому что java, расширенный для, не использует итератор списка внутри. Установка ожиданий для метода List.get() также не работает, поскольку улучшенная реализация, похоже, также не вызывает метод get() списка.

Любая помощь будет высоко ценится.


person HackerGil    schedule 16.06.2011    source источник


Ответы (4)


Издевательство над итератором работает для меня. См. ниже пример кода:

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);
    }
}
person hoipolloi    schedule 16.06.2011
comment
В вашем примере вы заранее знаете точное количество элементов, поэтому вы можете издеваться над `hasNext()`, что, если вы создаете эти элементы в цикле for, когда вы не знаете количество объектов заранее? - person Alexandru Barbarosie; 20.10.2015
comment
Любой ответ на вышеуказанный вопрос? Что делать, если мы не знаем количество итераций? - person Deepak S; 14.01.2018
comment
Если вы не знаете количество итераций (что немного странно в модульном тесте), то этот метод не может быть применен. - person hoipolloi; 16.01.2018

Если я что-то упустил, вы, вероятно, должны вернуть реальный список издевательских значений. В этом случае создайте свой список тестовых строк в методе генератора и просто верните его. В более сложных случаях вы можете заменить содержимое списка фиктивными объектами.

В заключение я не могу представить, зачем вам когда-либо действительно нужно было издеваться над расширенным циклом for. Природа модульных тестов не подходит для такого уровня проверки. Тем не менее это интересный вопрос.

person Andrew White    schedule 16.06.2011
comment
Полностью. Я знаю, что его можно протестировать, создав список моков. Мне просто интересно, что делает расширенный цикл for, потому что он ни использует итератор, ни вызывает метод get() списка в любое время. Как тогда он получает доступ к структуре данных? Интересно правда? - person HackerGil; 17.06.2011
comment
Кроме того, я попытался выполнить ваш код. Как только я переименовал переменную mockIterator в mockIter, тест проходит. - person hoipolloi; 17.06.2011

Просто хочу кое-что указать, потому что я боролся с этим весь день:

Если вы хотите использовать синтаксис myList.forEach(...) вместо for(:), вы должны включить (где вы настраиваете свой список издевательств):

doCallRealMethod().when(myMockedList).forEach(anyObject());
person OneWholeBurrito    schedule 24.06.2016
comment
anyObject() сейчас устарел, вы можете использовать: doCallRealMethod().when(myMockedList).forEach(any()); - person uwolfer; 08.09.2017

Вы хотите сделать что-то вроде этого.

/**
    * 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();

    }

Строковые комментарии и javadoc должны достаточно ясно указывать, как имитировать поведение любого итерируемого объекта, независимо от того, является ли он списком, коллекцией, javax.inject.instance или чем-то еще.

person 99Sono    schedule 27.06.2016