Насмешливый метод с универсальным функциональным интерфейсом в качестве аргумента — Mockito

Я работаю над приложением и решил протестировать его с помощью JUnit5 и Mockito. У меня есть функциональный интерфейс FunctionSQL<T, R>:

@FunctionalInterface
public interface FunctionSQL<T, R> {
    R apply(T arg) throws SQLException;
}

У меня также есть класс DataAccessLayer - конструктор, который получает databaseURL и connectionProperties, опущен из-за проблем с читабельностью:

public class DataAccessLayer {

    private String databaseURL;
    private Properties connectionProperties;

    public <R> R executeQuery(FunctionSQL<Connection, R> function){
        Connection conn = null;
        R result = null;
        try {
            synchronized (this) {
                conn = DriverManager.getConnection(databaseURL, connectionProperties);
            }

            result = function.apply(conn);

        } catch (SQLException ex) { }
        finally {
            closeConnection(conn);
        }

        return result;
    }

    private void closeConnection(Connection conn) {
        try {
            if (conn != null)
                conn.close();
        } catch (SQLException ex) { }
    }

И абстрактный класс репозитория:

public abstract class AbstractRepository {
    protected DataAccessLayer dataAccessLayer;

    public AbstractRepository() {
        dataAccessLayer = new DataAccessLayer();
    }
}

Я также создал реализацию репозитория:

public class ProgressRepository extends AbstractRepository {

    public List<ProgressEntity> getAll() {
        String sql = "SELECT * FROM progresses";
        return dataAccessLayer.executeQuery(connection -> {
            PreparedStatement statement = connection.prepareStatement(sql);

            ResultSet result = statement.executeQuery();


            List<ProgressEntity> progresses = new ArrayList<>();

            while (result.next()){
                ProgressEntity progressEntity = new ProgressEntity();
                progresses.add(progressEntity);
            }

            statement.close();
            return progresses;
        });
    }

Я попытался найти решение, позволяющее издеваться над методом executeQuery(...) из класса DataAccessLayer. Я хотел бы изменить connection, который используется в качестве лямбда-аргумента.

Я пробовал это:

class ProgressRepositoryTest {

    @Mock
    private static DataAccessLayer dataAccessLayer = new DataAccessLayer();

    private static Connection conn;

    @BeforeEach
    void connecting() throws SQLException {
        conn = DriverManager.getConnection("jdbc:h2:mem:test;", "admin", "admin");
    }

    @AfterEach
    void disconnecting() throws SQLException {
        conn.close();
    }

    @Test
    void getAllTest(){

        when(dataAccessLayer.executeQuery(ArgumentMatchers.<FunctionSQL<Connection, ProgressEntity>>any())).then(invocationOnMock -> {
            FunctionSQL<Connection, ProgressEntity> arg = invocationOnMock.getArgument(0);
            return arg.apply(conn);
        });

        ProgressRepository progressRepository = new ProgressRepository();

        progressRepository.getAll();

    }
}

Но я получаю сообщение об ошибке:

org.mockito.exceptions.misusing.InvalidUseOfMatchersException: 
You cannot use argument matchers outside of verification or stubbing.
Examples of correct usage of argument matchers:
    when(mock.get(anyInt())).thenReturn(null);
    doThrow(new RuntimeException()).when(mock).someVoidMethod(anyObject());
    verify(mock).someMethod(contains("foo"))

This message may appear after an NullPointerException if the last matcher is returning an object 
like any() but the stubbed method signature expect a primitive argument, in this case,
use primitive alternatives.
    when(mock.get(any())); // bad use, will raise NPE
    when(mock.get(anyInt())); // correct usage use

Also, this error might show up because you use argument matchers with methods that cannot be mocked.
Following methods *cannot* be stubbed/verified: final/private/equals()/hashCode().
Mocking methods declared on non-public parent classes is not supported.

Буду очень благодарен за решение моей проблемы. Спасибо за помощь заранее!


person Piotr    schedule 05.04.2020    source источник
comment
Несколько вещей. Вы инициализировали свои макеты с помощью MockitoAnnotations.initMocks(this); или @ExtendWith(MockitoExtension.class)? Вы объявляете @Mock, но затем сразу же инициализируете экземпляр класса. @Mock private static DataAccessLayer dataAccessLayer = new DataAccessLayer(); Должен быть просто @Mock private DataAccessLayer dataAccessLayer; Кроме того, DataAccessLayer — это последний класс, над которым вы не можете издеваться, если не включите mockito-inline.   -  person heX    schedule 05.04.2020
comment
Проблема возникает здесь when(dataAccessLayer.executeQuery(ArgumentMatchers.<FunctionSQL<Connection, ProgressEntity>>any())).then(invocationOnMock -> { FunctionSQL<Connection, ProgressEntity> arg = invocationOnMock.getArgument(0); return arg.apply(conn); }); У меня так получилось, что я звонил и никогда не ставил when(...).thenReturn(null);   -  person Miguel Avila    schedule 05.04.2020
comment
@hex вы правы со всеми своими предложениями, большое спасибо, это было так просто: D   -  person Piotr    schedule 05.04.2020
comment
Рад, что смог помочь! Опубликовано как ответ.   -  person heX    schedule 05.04.2020


Ответы (1)


Несколько вещей. Вы инициализировали свои макеты с помощью MockitoAnnotations.initMocks(this); или @ExtendWith(MockitoExtension.class)? Вы объявляете @Mock, но затем сразу же инициализируете экземпляр класса.

@Mock
private static DataAccessLayer dataAccessLayer = new DataAccessLayer();

Должно быть просто:

@Mock
private DataAccessLayer dataAccessLayer; 

Кроме того, DataAccessLayer — это последний класс, над которым вы не можете издеваться, если не включите mockito-inline.

person heX    schedule 05.04.2020