【问题标题】:How to Mock DataSource Dependency Injection Despite Being Accessible via Static Method尽管可以通过静态方法访问,如何模拟数据源依赖注入
【发布时间】:2017-01-07 01:12:52
【问题描述】:

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

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

我通过更改我的真实代码的逻辑来解决这个问题,以检查私有变量是否为空,如果是,则使用注入的 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();
    }
}

【问题讨论】:

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


    【解决方案1】:

    可以使用 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 进行模拟的更多信息。

    【讨论】:

    • 谢谢@asch,我很困惑你为什么要模拟DBSource.class,因为它不是静态的,而是.getInstance() 方法 静态的。如果我使用你建议的when(DataSource.getConnection()).thenReturn(connection);,那么它会返回一个引用DataSource 的错误,我假设你的意思是DBSource,它也会返回一个错误。我将setUp() 方法的前两行更改为状态:PowerMockito.mockStatic(DBSource.class); when(DBSource.getInstance().getConnection()).thenReturn(connection); 但这会导致 NullPointerException,说DBSource 为空(?)。我做错了什么?
    • 基本上:我得到一个带有when(DBSource.getInstance().getConnection()).thenReturn(conn‌​ection); 的 NullPointerException 异常,因为我们在嘲笑 DBSource.class 尽管事实上它根本不是静态的,但它是 .getInstance() 方法。你的意见?谢谢
    • 所以 getInstance 也应该被模拟。我已经更新了解决方案。
    • 您的代码导致此错误:org.powermock.reflect.exceptions.MethodNotFoundException: No methods matching the name(s) createStatement were found in the class hierarchy of class java.lang.Object .... 这是由此行引起的:when(connection.createStatement()).thenReturn(statement);。你能让代码工作吗?我想知道这是否是 this bug,尽管据说已经修复。
    • @Nova,我解释了使用 PowerMock 模拟静态方法的总体思路。因此,现在您可以避免添加仅用于测试的构造函数。我不能让你的代码工作——这是你的挑战。我经常使用 PowerMock - 它很有魅力。尝试按照我建议的帖子中的所有说明进行操作。再次查看您的代码。您提到的错误与此无关。
    猜你喜欢
    • 1970-01-01
    • 2015-06-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-06
    • 1970-01-01
    • 2017-05-02
    • 1970-01-01
    相关资源
    最近更新 更多