unit-testingmockingmockitointegration-testingpowermock

How to Mock DataSource Dependency Injection Despite Being Accessible via Static Method


I'm using Mockito, DBUnit and HSQLDB to unit test my database code. I'm also writing integration tests of course.

I'm having trouble understanding how to inject a mocked DataSource into the system under test (class I'm testing). The DataSource is used for connection pooling, and therefore other classes can call a static method in the same class in order to retrieve an instance of this DataSource. This means that the DataSource is not injected into any constructors, anywhere, and so my tests don't have any constructors to inject the mocked DataSource into.

I'm getting around this by altering the logic of my real code to check if a private variable is null, and if so then use the injected DataSource (bad design since it's only needed for tests), otherwise it calls the static method to retrieve the connection pool's source (better design).

How do I inject a mocked DataSource into a class that doesn't have a constructor set up to accept it, because it can instead just call the static method to retrieve the dependency?

Class to Test

public DBConnection(DBSource dbSource) {   // <--- Constructor only required for test purposes :(
        this.dbSource = dbSource;
    }

    public final void createCompsDB() {
        Connection conn = null;
        Statement statement = null;
        try {
            if(dbSource==null){ 
                conn = DBSource.getInstance().getConnection();
            }else{
                conn = dbSource.getConnection(); /** Likely bad design, since dbSource is only NOT null for tests, so that you can inject the mocked datasource :(  */
            }
            statement = conn.createStatement();
            statement.executeUpdate("CREATE DATABASE placesdb");
            System.out.println("Database created...");
        } catch (SQLException e) {
              // ...
            }
        } finally {
            // Close Resources... 
        }
    }
 }

Test Class -- Test Passes

public class DBConnectionTest {
        final Statement statement = mock(Statement.class);
        final Connection connection = mock(Connection.class);
        final DBSource dataSource = mock(DBSource.class);

    @Before
    public void setUp() throws SQLException, IOException, PropertyVetoException {
        when(dataSource.getConnection()).thenReturn(connection);
        when(connection.createStatement()).thenReturn(statement);
    }

    @Test
    public void testCreateCompDBIfNotAlready() throws Exception {
        DBConnection dbConnection = new DBConnection(localDB, dataSource); /** This constructor is only needed for testing :( . How do I avoid it since all the classes I need to test don't require the dependency to be injected? */
        dbConnection.createCompsDB();    
        verify(statement).executeUpdate("CREATE DATABASE PLACES");
    }
}

DBSource.java

protected DBSource() throws IOException, SQLException, PropertyVetoException {
        ds = new BasicDataSource();
        ds.setDriverClassName("org.postgresql.Driver");
        ds.setUsername("user");
        ds.setPassword("pass");
        ds.setUrl("jdbc:postgresql://localhost:5432/placesdb");
    }

    public static DBSource getInstance() {   // <--- Static method means dependent classes don't need to accept injections
        if (datasource == null) {
            datasource = new DBSource();
            return datasource;
        } else {
            return datasource;
        }
    }

    public Connection getConnection() throws SQLException {
        return this.ds.getConnection();
    }
}

Solution

  • Mocking of the static class methods may be done with PowerMockito. The test class should be something like this:

    @RunWith(PowerMockRunner.class)
    @PrepareForTest(DBSource.class)
    public class DBConnectionTest {
        @Mock
        final Statement statement;
        @Mock
        final Connection connection;
        @Mock
        final DBSource dbsource;
    
        @Before
        public void setUp() throws SQLException, IOException, PropertyVetoException {
            PowerMockito.mockStatic(DBSource.class);
            when(DbSource.getInstance()).thenReturn(dbsource);
            when(dbsource.getConnection()).thenReturn(connection);
            when(connection.createStatement()).thenReturn(statement);
        }
    
        @Test
        public void testCreateCompDBIfNotAlready() throws Exception {
            DBConnection dbConnection = new DBConnection(localDB); // No test-only constructor anymore
            dbConnection.createCompsDB();    
            verify(statement).executeUpdate("CREATE DATABASE PLACES");
        }
    }
    

    You can read here more about mocking with PowerMock.