如何正确模拟 bean HikariDataSource?
How to mock bean HikariDataSource correctly?
我使用 Mockito 编写了集成测试,但它在设置了与数据库的连接时有效。实际上测试只是检查访问一些端点的可能性,与数据访问层无关。所以我还不需要数据库。
数据库关闭时测试失败的原因 - HikariDatasource 在 spring 实例化上下文时检查与数据库的连接。模拟不 return 连接,它导致应用程序失败。我找到的解决方案是在内存数据库中使用 hsql,但对我来说它看起来像是解决方法。可能存在提供一些虚假数据的其他解决方案?
不确定这是不是优雅的解决方案,但我需要强制执行这样的工作测试
mockMvc.perform(
post("/some").contentType(MediaType.APPLICATION_JSON_UTF8)
.content(objectMapper.writeValueAsString(someDto))
.header(HttpHeaders.AUTHORIZATION, AUTH_HEADER)
.accept(MediaType.APPLICATION_JSON_UTF8)
).andExpect(status().is(201));
经过调试和搜索,我找到了允许在内存中没有数据库的情况下启动容器的解决方案。
@TestConfiguration
@ComponentScan(basePackages = "com.test")
@ActiveProfiles("test")
public class TestConfig {
//Other Beans
@Bean
public DataSource getDatasource() {
return new MockDataSource();
}
}
class MockDataSource implements DataSource {
@Override
public Connection getConnection() throws SQLException {
return createMockConnection();
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return getConnection();
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
public static Connection createMockConnection() throws SQLException {
// Setup mock connection
final Connection mockConnection = mock(Connection.class);
// Autocommit is always true by default
when(mockConnection.getAutoCommit()).thenReturn(true);
// Handle Connection.createStatement()
Statement statement = mock(Statement.class);
when(mockConnection.createStatement()).thenReturn(statement);
when(mockConnection.createStatement(anyInt(), anyInt())).thenReturn(statement);
when(mockConnection.createStatement(anyInt(), anyInt(), anyInt())).thenReturn(statement);
when(mockConnection.isValid(anyInt())).thenReturn(true);
// Handle Connection.prepareStatement()
PreparedStatement mockPreparedStatement = mock(PreparedStatement.class);
when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement);
when(mockConnection.prepareStatement(anyString(), anyInt())).thenReturn(mockPreparedStatement);
when(mockConnection.prepareStatement(anyString(), (int[]) any())).thenReturn(mockPreparedStatement);
when(mockConnection.prepareStatement(anyString(), (String[]) any())).thenReturn(mockPreparedStatement);
when(mockConnection.prepareStatement(anyString(), anyInt(), anyInt())).thenReturn(mockPreparedStatement);
when(mockConnection.prepareStatement(anyString(), anyInt(), anyInt(), anyInt())).thenReturn(mockPreparedStatement);
doAnswer((Answer<Void>) invocation -> null).doNothing().when(mockPreparedStatement).setInt(anyInt(), anyInt());
ResultSet mockResultSet = mock(ResultSet.class);
when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet);
when(mockResultSet.getString(anyInt())).thenReturn("aString");
when(mockResultSet.next()).thenReturn(true);
// Handle Connection.prepareCall()
CallableStatement mockCallableStatement = mock(CallableStatement.class);
when(mockConnection.prepareCall(anyString())).thenReturn(mockCallableStatement);
when(mockConnection.prepareCall(anyString(), anyInt(), anyInt())).thenReturn(mockCallableStatement);
when(mockConnection.prepareCall(anyString(), anyInt(), anyInt(), anyInt())).thenReturn(mockCallableStatement);
ResultSet mockResultSetTypeInfo = mock(ResultSet.class);
DatabaseMetaData mockDataBaseMetadata = mock(DatabaseMetaData.class);
when(mockDataBaseMetadata.getDatabaseProductName()).thenReturn("PostgreSQL");
when(mockDataBaseMetadata.getDatabaseMajorVersion()).thenReturn(8);
when(mockDataBaseMetadata.getDatabaseMinorVersion()).thenReturn(2);
when(mockDataBaseMetadata.getConnection()).thenReturn(mockConnection);
when(mockDataBaseMetadata.getTypeInfo()).thenReturn(mockResultSetTypeInfo);
when(mockConnection.getMetaData()).thenReturn(mockDataBaseMetadata);
// Handle Connection.close()
doAnswer((Answer<Void>) invocation -> null).doThrow(new SQLException("Connection is already closed")).when(mockConnection).close();
// Handle Connection.commit()
doAnswer((Answer<Void>) invocation -> null).doThrow(new SQLException("Transaction already committed")).when(mockConnection).commit();
// Handle Connection.rollback()
doAnswer((Answer<Void>) invocation -> null).doThrow(new SQLException("Transaction already rolled back")).when(mockConnection).rollback();
return mockConnection;
}
}
Mocking DataSource 允许启动容器并使用 MockMvc 提供 post 对控制器的调用。
我使用 Mockito 编写了集成测试,但它在设置了与数据库的连接时有效。实际上测试只是检查访问一些端点的可能性,与数据访问层无关。所以我还不需要数据库。 数据库关闭时测试失败的原因 - HikariDatasource 在 spring 实例化上下文时检查与数据库的连接。模拟不 return 连接,它导致应用程序失败。我找到的解决方案是在内存数据库中使用 hsql,但对我来说它看起来像是解决方法。可能存在提供一些虚假数据的其他解决方案?
不确定这是不是优雅的解决方案,但我需要强制执行这样的工作测试
mockMvc.perform( post("/some").contentType(MediaType.APPLICATION_JSON_UTF8) .content(objectMapper.writeValueAsString(someDto)) .header(HttpHeaders.AUTHORIZATION, AUTH_HEADER) .accept(MediaType.APPLICATION_JSON_UTF8) ).andExpect(status().is(201));
经过调试和搜索,我找到了允许在内存中没有数据库的情况下启动容器的解决方案。
@TestConfiguration
@ComponentScan(basePackages = "com.test")
@ActiveProfiles("test")
public class TestConfig {
//Other Beans
@Bean
public DataSource getDatasource() {
return new MockDataSource();
}
}
class MockDataSource implements DataSource {
@Override
public Connection getConnection() throws SQLException {
return createMockConnection();
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return getConnection();
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
public static Connection createMockConnection() throws SQLException {
// Setup mock connection
final Connection mockConnection = mock(Connection.class);
// Autocommit is always true by default
when(mockConnection.getAutoCommit()).thenReturn(true);
// Handle Connection.createStatement()
Statement statement = mock(Statement.class);
when(mockConnection.createStatement()).thenReturn(statement);
when(mockConnection.createStatement(anyInt(), anyInt())).thenReturn(statement);
when(mockConnection.createStatement(anyInt(), anyInt(), anyInt())).thenReturn(statement);
when(mockConnection.isValid(anyInt())).thenReturn(true);
// Handle Connection.prepareStatement()
PreparedStatement mockPreparedStatement = mock(PreparedStatement.class);
when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement);
when(mockConnection.prepareStatement(anyString(), anyInt())).thenReturn(mockPreparedStatement);
when(mockConnection.prepareStatement(anyString(), (int[]) any())).thenReturn(mockPreparedStatement);
when(mockConnection.prepareStatement(anyString(), (String[]) any())).thenReturn(mockPreparedStatement);
when(mockConnection.prepareStatement(anyString(), anyInt(), anyInt())).thenReturn(mockPreparedStatement);
when(mockConnection.prepareStatement(anyString(), anyInt(), anyInt(), anyInt())).thenReturn(mockPreparedStatement);
doAnswer((Answer<Void>) invocation -> null).doNothing().when(mockPreparedStatement).setInt(anyInt(), anyInt());
ResultSet mockResultSet = mock(ResultSet.class);
when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet);
when(mockResultSet.getString(anyInt())).thenReturn("aString");
when(mockResultSet.next()).thenReturn(true);
// Handle Connection.prepareCall()
CallableStatement mockCallableStatement = mock(CallableStatement.class);
when(mockConnection.prepareCall(anyString())).thenReturn(mockCallableStatement);
when(mockConnection.prepareCall(anyString(), anyInt(), anyInt())).thenReturn(mockCallableStatement);
when(mockConnection.prepareCall(anyString(), anyInt(), anyInt(), anyInt())).thenReturn(mockCallableStatement);
ResultSet mockResultSetTypeInfo = mock(ResultSet.class);
DatabaseMetaData mockDataBaseMetadata = mock(DatabaseMetaData.class);
when(mockDataBaseMetadata.getDatabaseProductName()).thenReturn("PostgreSQL");
when(mockDataBaseMetadata.getDatabaseMajorVersion()).thenReturn(8);
when(mockDataBaseMetadata.getDatabaseMinorVersion()).thenReturn(2);
when(mockDataBaseMetadata.getConnection()).thenReturn(mockConnection);
when(mockDataBaseMetadata.getTypeInfo()).thenReturn(mockResultSetTypeInfo);
when(mockConnection.getMetaData()).thenReturn(mockDataBaseMetadata);
// Handle Connection.close()
doAnswer((Answer<Void>) invocation -> null).doThrow(new SQLException("Connection is already closed")).when(mockConnection).close();
// Handle Connection.commit()
doAnswer((Answer<Void>) invocation -> null).doThrow(new SQLException("Transaction already committed")).when(mockConnection).commit();
// Handle Connection.rollback()
doAnswer((Answer<Void>) invocation -> null).doThrow(new SQLException("Transaction already rolled back")).when(mockConnection).rollback();
return mockConnection;
}
}
Mocking DataSource 允许启动容器并使用 MockMvc 提供 post 对控制器的调用。