【问题标题】:Mockito: Mock a resultSet depending on a collection of expected objectsMockito:根据预期对象的集合模拟结果集
【发布时间】:2020-08-06 05:03:29
【问题描述】:

我想编写多个依赖于调用数据库并从 ResultSet 构建对象的 JUnit 测试。

这个想法是不同的测试用例需要来自数据库的不同数量的元素。因此,next方法返回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)

有没有办法动态地做这个存根?

谢谢!

【问题讨论】:

    标签: java mockito


    【解决方案1】:

    您可以通过以下方法实现:

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

    注意事项:

    解决方案 2

    您可以使用有状态的枚举器和 thenAnswer,恕我直言,这会产生一个非常简洁的解决方案:

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

    【讨论】:

    • 哇!有效。我曾尝试过执行三个单独的循环,但我从来没有想过要使用这个正在运行的 Stub = 运行 Stub.thenReturn(...)。是的,resultSet 被嘲笑了两次,我没有在真实的测试用例上犯错误。
    • 另见解决方案 2,它避免了使用 thenAnswer 的所有问题
    • 是的,解决方案 2 看起来也很简洁。它可以很好地扩展到更复杂的对象。您只需将 getXXX(1) 与 field1 配对,将 getXXX(2) 与 field2 配对,以此类推。不错!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-02-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多