Andrey
Andrey

Reputation: 158

How to mock bean HikariDataSource correctly?

I wrote integration test using Mockito, but it works when connection to database was set. Actually test just check possibility access some endpoints and not related to the data access layer. So I don't need database for it yet. Reason of failing test when database is down - HikariDatasource check connection to the database when spring instantiates context. Mocking doesn't return Connection and it lead to the fail of application. Solution that i have found is use hsql in memory database, but for me it looks like work around. Probably exists other solution providing some fake data?

Upvotes: 2

Views: 4204

Answers (1)

Andrey
Andrey

Reputation: 158

Not sure that this is elegant solution, but I need to force work tests like this

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));

After debugging and searching I have found solution that allowed start container without database in memory.

@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 allows start container and provide post call to the controller using MockMvc.

Upvotes: 1

Related Questions