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();
}
}
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.