尽管可以通过静态方法访问,但如何模拟DataSource依赖注入

时间:2017-01-07 01:12:53

标签: unit-testing mocking mockito integration-testing powermock

我使用MockitoDBUnitHSQLDB对我的数据库代码进行单元测试。我当然也在编写集成测试。

我无法理解如何将被模拟的DataSource注入被测系统(I类测试)。 DataSource用于连接池,因此其他类可以在同一个类中调用static方法,以便检索此DataSource的实例。这意味着DataSource不会在任何地方注入任何构造函数,因此我的测试没有任何构造函数可以将模拟的DataSource注入其中。

我通过改变我的实际代码的逻辑来检查私有变量是否为null,如果是,那么使用注入的DataSource(设计不好,因为它只需要测试) ,否则它调用静态方法来检索连接池的源(更好的设计)。

如何将一个模拟的DataSource注入一个没有构造函数设置为接受它的类,因为它可以只调用静态方法来检索依赖项?

要测试的课程

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... 
        }
    }
 }

测试类 - 测试通行证

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

1 个答案:

答案 0 :(得分:3)

可以使用PowerMockito完成静态类方法的模拟。 测试类应该是这样的:

@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");
    }
}

您可以阅读here有关使用PowerMock进行模拟的更多信息。