Mockito: макет результирующего набора в зависимости от коллекции ожидаемых объектов - PullRequest
1 голос
/ 23 апреля 2020

Я хочу написать несколько тестов JUnit, которые зависят от вызова базы данных и построения объектов из ResultSet.

Идея состоит в том, что для разных тестовых примеров требуется разное количество элементов, поступающих из базы данных. Поэтому число раз, когда следующий метод возвращает значение true, и вызов getString и подобных методов будет различаться.

Вот как я пытался это сделать, но он выдает исключение, показанное ниже.


import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Collection;
import java.util.LinkedList;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.stubbing.OngoingStubbing;

@RunWith(MockitoJUnitRunner.class)
public class ForStackOverflow {
  private static class Rule {
    int id;
    String name;
  }

  private static final String SQL = "select * from table";

  @Mock
  private Connection connection;

  @Mock
  private PreparedStatement statement;

  @Mock
  private ResultSet resultSet;

  @Before
  public void setup() throws Exception {
    // 
    databaseManager.connection = connection;
    when(connection.prepareStatement(SQL)).thenReturn(statement);
    when(statement.executeQuery()).thenReturn(resultSet);
  }

  @Test
  public void testSomething() throws Exception {
    // setup
    Rule rule;
    Collection<Rule> rules = new LinkedList<>();
    rule = new Rule();
    rule.id = 0;
    rule.name = "mock name 1";
    rules.add(rule);

    rule = new Rule();
    rule.id = 1;
    rule.name = "mock name 2";
    rules.add(rule);

    ResultSet resultSet = setupResultSet(rules);
    databaseManager.queryDatabase();

    verify(resultSet, times(rules.size() + 1)).next();
    verify(resultSet, times(rules.size())).getInt(1);
    verify(resultSet, times(rules.size())).getString(2);
  }

  private ResultSet setupResultSet(Collection<Rule> rules) throws Exception {
    ResultSet resultSet = mock(ResultSet.class);
    OngoingStubbing<Boolean> ongoingWhen = when(resultSet.next());
    OngoingStubbing<Integer> ongoingGetInt = when(resultSet.getInt(1));
    OngoingStubbing<String> ongoingGetString = when(resultSet.getString(2));
    for (Rule rulePriority : rules) {
      ongoingWhen.thenReturn(true);
      ongoingGetInt.thenReturn(rulePriority.id);
      ongoingGetString.thenReturn(rulePriority.name);
    }
    ongoingWhen.thenReturn(false);

    return resultSet;
  }

}

Трассировка стека

org.mockito.exceptions.misusing.UnfinishedStubbingException: 
Unfinished stubbing detected here:
-> at ForStackOverflow.setupResultSet(ForStackOverflow.java:72)

E.g. thenReturn() may be missing.
Examples of correct stubbing:
    when(mock.isOk()).thenReturn(true);
    when(mock.isOk()).thenThrow(exception);
    doThrow(exception).when(mock).someVoidMethod();
Hints:
 1. missing thenReturn()
 2. you are trying to stub a final method, which is not supported
 3. you are stubbing the behaviour of another mock inside before 'thenReturn' instruction is completed

    at ForStackOverflow.setupResultSet(ForStackOverflow.java:73)
    at ForStackOverflow.testSomething(ForStackOverflow.java:62)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

Есть ли способ выполнить эту заглушку динамически?

Спасибо!

1 Ответ

0 голосов
/ 24 апреля 2020

Этого можно добиться с помощью следующего метода:

private ResultSet setupResultSet(Collection<Rule> rules) throws Exception {
    OngoingStubbing<Boolean> ongoingWhen = when(resultSet.next());
    for (Rule rulePriority : rules) {
        ongoingWhen = ongoingWhen.thenReturn(true);
    }
    ongoingWhen.thenReturn(false);
    OngoingStubbing<Integer> ongoingGetInt = when(resultSet.getInt(1));
    for (Rule rulePriority : rules) {
        ongoingGetInt = ongoingGetInt.thenReturn(rulePriority.id);
    }
    OngoingStubbing<String> ongoingGetString = when(resultSet.getString(2));
    for (Rule rulePriority : rules) {
        ongoingGetString = ongoingGetString.thenReturn(rulePriority.name);
    }
    return resultSet;
}

Примечания:

  • Вы можете использовать цепочку для записи нескольких ответов на один и тот же метод с одинаковыми аргументами. См .:
  • Вы не можете взаимодействовать с издеваться, пока идет заглушка. Вот почему я записываю ожидания в 3 цикла. См .:
  • Вы взаимодействовали с неправильным экземпляром макета resultSet (вам не нужно дважды имитировать).

Решение 2

Вы можете использовать перечислитель с отслеживанием состояния и затем Отвечать ИМХО, что приводит к довольно аккуратному решению:

static class Enumerator<E> {
    private final Iterator<E> iterator;
    private E current = null;

    public Enumerator(Iterator<E> iterator) {
        this.iterator = iterator;
    }

    public boolean next() {
        if (iterator.hasNext()) {
            this.current = iterator.next();
            return true;
        } else {
            return false;
        }
    }

    public E getCurrent() {
        return current;
    }
}

private void setupResultSet(Collection<Rule> rules) throws Exception {
    var resultSetEnumerator = new Enumerator<>(rules.iterator());
    when(resultSet.next()).thenAnswer(invocation -> resultSetEnumerator.next());
    when(resultSet.getInt(1)).thenAnswer(invocation -> resultSetEnumerator.getCurrent().id);
    when(resultSet.getString(2)).thenAnswer(invocation -> resultSetEnumerator.getCurrent().name);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...