简体   繁体   English

Mockito:根据预期对象的集合模拟结果集

[英]Mockito: Mock a resultSet depending on a collection of expected objects

I want to write multiple JUnit tests that depend on calling a database and building objects out of a ResultSet.我想编写多个 JUnit 测试,这些测试依赖于调用数据库并从 ResultSet 构建对象。

The idea is that different test cases need different number of elements coming from the database.这个想法是不同的测试用例需要来自数据库的不同数量的元素。 Therefore, the number of times the next method returns true, and the call to getString and similar methods will vary.因此,next 方法返回 true 的次数,以及对getString和类似方法的调用会有所不同。

This is how I tried to do it, but it's throwing the exception shown below.这就是我尝试这样做的方式,但它抛出了下面显示的异常。


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

}

The Stack trace堆栈跟踪

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)

Is there a way to do this stubbing dynamically?有没有办法动态地做这个存根?

Thank you!谢谢!

You can achieve this with following method:您可以通过以下方法实现此目的:

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

Notes:笔记:

Solution 2解决方案 2

You can use a stateful enumerator and thenAnswer, which IMHO results in a pretty neat solution:您可以使用有状态的枚举器和 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);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM