1

I want to write multiple JUnit tests that depend on calling a database and building objects out of a 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.

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!

AEM
  • 1,354
  • 8
  • 20
  • 30

1 Answers1

0

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

You can use a stateful enumerator and thenAnswer, which IMHO results in a pretty neat solution:

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);
}
Lesiak
  • 22,088
  • 2
  • 41
  • 65
  • Wow! It worked. I had tried doing three separate loops, but that ongoingStub = ongoingStub.thenReturn(...), I would never had thought of that. And yes, the resultSet was mocked twice, I didn't make the mistake on the real test case. – Juan Carlos Muñoz Apr 24 '20 at 05:19
  • See also solution 2, it avoids all the gotchas with using thenAnswer – Lesiak Apr 24 '20 at 06:04
  • Yes, solution 2 also looks pretty neat. It expands well for more complex objects. All you need is to pair the getXXX(1) to field1, getXXX(2) to field2, and so on. Nice! – Juan Carlos Muñoz Apr 24 '20 at 18:11