英文:
Mocking method with generic functional interface as an argument - 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;
});
}
}
我试图找到一种方法来模拟 DataAccessLayer
类中的 executeQuery(...)
方法。我希望能够更改作为 lambda 参数传递的 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 a NullPointerException if the last matcher is returning an object
like any() but the stubbed method signature expects 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.
对于我遇到的问题的解决方案,我将不胜感激。提前谢谢您的帮助!
英文:
I am working on app and I decided to test it with JUnit5 and Mockito. I have a functional interface FunctionSQL<T, R>
:
@FunctionalInterface
public interface FunctionSQL<T, R> {
R apply(T arg) throws SQLException;
}
I have also DataAccessLayer class - constructor which obtains the databaseURL
and connectionProperties
is omitted due to readability issues:
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) { }
}
And abstract repository class:
public abstract class AbstractRepository {
protected DataAccessLayer dataAccessLayer;
public AbstractRepository() {
dataAccessLayer = new DataAccessLayer();
}
}
I have also created a implementation of repository:
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;
});
}
I have tried to find a solution to mock the executeQuery(...)
method from DataAccessLayer
class. I would like like to change the connection
which is used as lambda argument.
I have tried this one:
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();
}
}
But I'm getting an error:
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.
I would be very grateful for the solution of my problem.
Thanks for help in advance!
答案1
得分: 2
几件事情。你是否使用了MockitoAnnotations.initMocks(this);
或@ExtendWith(MockitoExtension.class)
来初始化你的模拟对象?你声明了一个@Mock
,然后立即初始化了该类的一个实例。
@Mock
private static DataAccessLayer dataAccessLayer = new DataAccessLayer();
应该改为:
@Mock
private DataAccessLayer dataAccessLayer;
另外,DataAccessLayer是一个final类,你无法进行模拟,除非你使用mockito-inline。
英文:
Few things. Did you initialize your mocks with either MockitoAnnotations.initMocks(this);
or @ExtendWith(MockitoExtension.class)
? You declare a @Mock
but then you initialize an instance of the class right away.
@Mock
private static DataAccessLayer dataAccessLayer = new DataAccessLayer();
Should just be:
@Mock
private DataAccessLayer dataAccessLayer;
Also DataAccessLayer is a final class which you cannot mock unless you include mockito-inline.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论