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

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

我正在使用 Mockito, DBUnit and HSQLDB 对我的数据库代码进行单元测试。我当然也在写集成测试。

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

我通过改变我的真实代码的逻辑来解决这个问题,以检查私有变量是否为空,如果是,则使用注入的数据源(糟糕的设计,因为它只用于测试),否则它调用检索连接池源的静态方法(更好的设计)。

我如何将模拟的 DataSource 注入到没有设置接受它的构造函数的 class 中,因为它只能调用静态方法来检索依赖项?

Class 测试

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

测试Class -- 测试通过

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

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

@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 模拟的信息。