Mockito中使用通用函数接口作为参数来模拟方法

huangapple go评论138阅读模式
英文:

Mocking method with generic functional interface as an argument - Mockito

问题

我正在开发一个应用程序,并决定使用JUnit5和Mockito进行测试。我有一个功能接口 FunctionSQL<T, R>

  1. @FunctionalInterface
  2. public interface FunctionSQL<T, R> {
  3. R apply(T arg) throws SQLException;
  4. }

我还有一个 DataAccessLayer 类 - 构造函数获取了 databaseURLconnectionProperties,为了可读性问题已省略:

  1. public class DataAccessLayer {
  2. private String databaseURL;
  3. private Properties connectionProperties;
  4. public <R> R executeQuery(FunctionSQL<Connection, R> function){
  5. Connection conn = null;
  6. R result = null;
  7. try {
  8. synchronized (this) {
  9. conn = DriverManager.getConnection(databaseURL, connectionProperties);
  10. }
  11. result = function.apply(conn);
  12. } catch (SQLException ex) { }
  13. finally {
  14. closeConnection(conn);
  15. }
  16. return result;
  17. }
  18. private void closeConnection(Connection conn) {
  19. try {
  20. if (conn != null)
  21. conn.close();
  22. } catch (SQLException ex) { }
  23. }
  24. }

还有一个抽象的仓库类:

  1. public abstract class AbstractRepository {
  2. protected DataAccessLayer dataAccessLayer;
  3. public AbstractRepository() {
  4. dataAccessLayer = new DataAccessLayer();
  5. }
  6. }

我还创建了一个仓库的实现:

  1. public class ProgressRepository extends AbstractRepository {
  2. public List<ProgressEntity> getAll() {
  3. String sql = "SELECT * FROM progresses";
  4. return dataAccessLayer.executeQuery(connection -> {
  5. PreparedStatement statement = connection.prepareStatement(sql);
  6. ResultSet result = statement.executeQuery();
  7. List<ProgressEntity> progresses = new ArrayList<>();
  8. while (result.next()){
  9. ProgressEntity progressEntity = new ProgressEntity();
  10. progresses.add(progressEntity);
  11. }
  12. statement.close();
  13. return progresses;
  14. });
  15. }
  16. }

我试图找到一种方法来模拟 DataAccessLayer 类中的 executeQuery(...) 方法。我希望能够更改作为 lambda 参数传递的 connection

我尝试了这个方法:

  1. class ProgressRepositoryTest {
  2. @Mock
  3. private static DataAccessLayer dataAccessLayer = new DataAccessLayer();
  4. private static Connection conn;
  5. @BeforeEach
  6. void connecting() throws SQLException {
  7. conn = DriverManager.getConnection("jdbc:h2:mem:test;", "admin", "admin");
  8. }
  9. @AfterEach
  10. void disconnecting() throws SQLException {
  11. conn.close();
  12. }
  13. @Test
  14. void getAllTest(){
  15. when(dataAccessLayer.executeQuery(ArgumentMatchers.<FunctionSQL<Connection, ProgressEntity>>any())).then(invocationOnMock -> {
  16. FunctionSQL<Connection, ProgressEntity> arg = invocationOnMock.getArgument(0);
  17. return arg.apply(conn);
  18. });
  19. ProgressRepository progressRepository = new ProgressRepository();
  20. progressRepository.getAll();
  21. }
  22. }

但是我得到了一个错误:

  1. org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
  2. You cannot use argument matchers outside of verification or stubbing.
  3. Examples of correct usage of argument matchers:
  4. when(mock.get(anyInt())).thenReturn(null);
  5. doThrow(new RuntimeException()).when(mock).someVoidMethod(anyObject());
  6. verify(mock).someMethod(contains("foo"))
  7. This message may appear after a NullPointerException if the last matcher is returning an object
  8. like any() but the stubbed method signature expects a primitive argument; in this case,
  9. use primitive alternatives.
  10. when(mock.get(any())); // bad use, will raise NPE
  11. when(mock.get(anyInt())); // correct usage use
  12. Also, this error might show up because you use argument matchers with methods that cannot be mocked.
  13. Following methods *cannot* be stubbed/verified: final/private/equals()/hashCode().
  14. 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>:

  1. @FunctionalInterface
  2. public interface FunctionSQL<T, R> {
  3. R apply(T arg) throws SQLException;
  4. }

I have also DataAccessLayer class - constructor which obtains the databaseURL and connectionProperties is omitted due to readability issues:

  1. public class DataAccessLayer {
  2. private String databaseURL;
  3. private Properties connectionProperties;
  4. public <R> R executeQuery(FunctionSQL<Connection, R> function){
  5. Connection conn = null;
  6. R result = null;
  7. try {
  8. synchronized (this) {
  9. conn = DriverManager.getConnection(databaseURL, connectionProperties);
  10. }
  11. result = function.apply(conn);
  12. } catch (SQLException ex) { }
  13. finally {
  14. closeConnection(conn);
  15. }
  16. return result;
  17. }
  18. private void closeConnection(Connection conn) {
  19. try {
  20. if (conn != null)
  21. conn.close();
  22. } catch (SQLException ex) { }
  23. }

And abstract repository class:

  1. public abstract class AbstractRepository {
  2. protected DataAccessLayer dataAccessLayer;
  3. public AbstractRepository() {
  4. dataAccessLayer = new DataAccessLayer();
  5. }
  6. }

I have also created a implementation of repository:

  1. public class ProgressRepository extends AbstractRepository {
  2. public List<ProgressEntity> getAll() {
  3. String sql = "SELECT * FROM progresses";
  4. return dataAccessLayer.executeQuery(connection -> {
  5. PreparedStatement statement = connection.prepareStatement(sql);
  6. ResultSet result = statement.executeQuery();
  7. List<ProgressEntity> progresses = new ArrayList<>();
  8. while (result.next()){
  9. ProgressEntity progressEntity = new ProgressEntity();
  10. progresses.add(progressEntity);
  11. }
  12. statement.close();
  13. return progresses;
  14. });
  15. }

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:

  1. class ProgressRepositoryTest {
  2. @Mock
  3. private static DataAccessLayer dataAccessLayer = new DataAccessLayer();
  4. private static Connection conn;
  5. @BeforeEach
  6. void connecting() throws SQLException {
  7. conn = DriverManager.getConnection("jdbc:h2:mem:test;", "admin", "admin");
  8. }
  9. @AfterEach
  10. void disconnecting() throws SQLException {
  11. conn.close();
  12. }
  13. @Test
  14. void getAllTest(){
  15. when(dataAccessLayer.executeQuery(ArgumentMatchers.<FunctionSQL<Connection, ProgressEntity>>any())).then(invocationOnMock -> {
  16. FunctionSQL<Connection, ProgressEntity> arg = invocationOnMock.getArgument(0);
  17. return arg.apply(conn);
  18. });
  19. ProgressRepository progressRepository = new ProgressRepository();
  20. progressRepository.getAll();
  21. }
  22. }

But I'm getting an error:

  1. org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
  2. You cannot use argument matchers outside of verification or stubbing.
  3. Examples of correct usage of argument matchers:
  4. when(mock.get(anyInt())).thenReturn(null);
  5. doThrow(new RuntimeException()).when(mock).someVoidMethod(anyObject());
  6. verify(mock).someMethod(contains("foo"))
  7. This message may appear after an NullPointerException if the last matcher is returning an object
  8. like any() but the stubbed method signature expect a primitive argument, in this case,
  9. use primitive alternatives.
  10. when(mock.get(any())); // bad use, will raise NPE
  11. when(mock.get(anyInt())); // correct usage use
  12. Also, this error might show up because you use argument matchers with methods that cannot be mocked.
  13. Following methods *cannot* be stubbed/verified: final/private/equals()/hashCode().
  14. 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,然后立即初始化了该类的一个实例。

  1. @Mock
  2. private static DataAccessLayer dataAccessLayer = new DataAccessLayer();

应该改为:

  1. @Mock
  2. 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.

  1. @Mock
  2. private static DataAccessLayer dataAccessLayer = new DataAccessLayer();

Should just be:

  1. @Mock
  2. private DataAccessLayer dataAccessLayer;

Also DataAccessLayer is a final class which you cannot mock unless you include mockito-inline.

huangapple
  • 本文由 发表于 2020年4月6日 02:23:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/61047397.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定