【问题标题】:How to mock DriverManager.getConnection(...)?如何模拟 DriverManager.getConnection(...)?
【发布时间】:2013-10-19 10:23:35
【问题描述】:

我有一个类,它连接到一个 H2 数据库并运行多个 SQL 语句。

public class H2Persistence implements IPersistence {

    private Connection conn;

    @Override
    public void open() {
        try
        {
            Class.forName("org.h2.Driver");
            conn = DriverManager.getConnection(CONN_TYPE_USER_HOME);

            final Statement stmt = conn.createStatement();

            stmt.executeUpdate("CREATE TABLE PERSON(" +
                    "ID BIGINT,"+
                    "AGEGROUP VARCHAR(255),"+
                    "MONTHLY_INCOME_LEVEL VARCHAR(255)," +
                    "GENDER VARCHAR(1),"+
                    "HOUSEHOLD_ID BIGINT)");

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
...
}

我想编写一个单元测试,它验证在open方法中执行了某个SQL语句(DROP TABLE IF EXISTS PERSON)。

为了做到这一点,我编写了以下测试:

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.when;


@RunWith(PowerMockRunner.class)
@PrepareForTest(DriverManager.class)
public class H2PersistenceTest {
    @Test
    public void testDropPersonIsCalled() throws SQLException {
        final Statement statement = mock(Statement.class);

        final Connection connection = mock(Connection.class);

        when(connection.createStatement()).thenReturn(statement);

        mockStatic(DriverManager.class);

        when(DriverManager.getConnection(H2Persistence.CONN_TYPE_USER_HOME)).thenReturn
                (connection);


        final H2Persistence objectUnderTest = new H2Persistence();

        objectUnderTest.open();
        verify(statement.executeUpdate("DROP TABLE IF EXISTS PERSON"));
    }
}

但它不起作用 - DriverManager 不是模拟连接,而是返回真实连接。

我该如何解决(让DriverManager在测试中返回连接模拟)?

这是我项目的pom.xml,可能有问题。

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>ru.mycompany</groupId>
    <artifactId>myproduct</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <powermock.version>1.5.1</powermock.version>
        <maven.compiler.source>1.6</maven.compiler.source>
        <maven.compiler.target>1.6</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.easytesting</groupId>
            <artifactId>fest-util</artifactId>
            <version>1.2.3</version>
        </dependency>
        <dependency>
            <groupId>org.easytesting</groupId>
            <artifactId>fest-assert-core</artifactId>
            <version>2.0M8</version>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>15.0</version>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-all</artifactId>
            <version>1.9.5</version>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>1.3.173</version>
        </dependency>
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-module-junit4</artifactId>
            <version>${powermock.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-api-mockito</artifactId>
            <version>${powermock.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

【问题讨论】:

    标签: java unit-testing mocking mockito powermock


    【解决方案1】:

    这个有效(注意导入):

    import static org.easymock.EasyMock.expect;
    import static org.mockito.Mockito.mock;
    import static org.mockito.Mockito.verify;
    import static org.mockito.Mockito.when;
    import static org.powermock.api.easymock.PowerMock.mockStatic;
    import static org.powermock.api.easymock.PowerMock.replay;
    
    
    @RunWith(PowerMockRunner.class)
    @PrepareForTest({DriverManager.class, H2Persistence.class})
    public class H2PersistenceTest {
        @Test
        public void testDropPersonIsCalled() throws SQLException {
            final Statement statement = mock(Statement.class);
    
            final Connection connection = mock(Connection.class);
    
            when(connection.createStatement()).thenReturn(statement);
    
            mockStatic(DriverManager.class);
    
            expect(DriverManager.getConnection(H2Persistence.CONN_TYPE_USER_HOME))
                    .andReturn(connection);
            expect(DriverManager.getConnection(null))
                    .andReturn(null);
    
            replay(DriverManager.class);
            final H2Persistence objectUnderTest = new H2Persistence();
    
            objectUnderTest.open();
    
            verify(statement).executeUpdate("DROP TABLE IF EXISTS PERSON");
            verify(statement).executeUpdate(H2Persistence.CREATE_TABLE_PERSON);
        }
    }
    

    【讨论】:

      【解决方案2】:

      执行此操作的通常方法是将连接创建分解到另一个类中,然后将其实例注入到相关类中。然后,您可以模拟该新类。

      在你的情况下,是这样的:

      public class H2Persistence implements IPersistence {
          private final ConnectionFactory connectionFactory;
          private Connection conn;
      
          public H2Persistence(ConnectionFactory connectionFactory) {
              this.connectionFactory = connectionFactory;
          }
      
          @Override
          public void open() {
              try {
                  conn = connectionFactory.createConnection(CONN_TYPE_USER_HOME);
                  // etc
              }
              catch (ClassNotFoundException e) {
                  e.printStackTrace();
              }
              catch (SQLException e) {
                  e.printStackTrace();
              }
          }
      }
      
      public class ConnectionFactory {
      
          Connection createConnection(String connType) throws SQLException, ClassNotFoundException {
              Class.forName("org.h2.Driver");
              return DriverManager.getConnection(connType);
          }
      
      }
      

      在这种特殊情况下,最好使用标准 JDBC 接口 DataSource 而不是您自己的连接工厂类:

      public class H2Persistence implements IPersistence {
          private final DataSource dataSource;
          private Connection conn;
      
          public H2Persistence(DataSource dataSource) {
              this.dataSource = dataSource;
          }
      
          @Override
          public void open() {
              try {
                  conn = dataSource.getConnection();
                  // etc
              }
              catch (SQLException e) {
                  e.printStackTrace();
              }
          }
      }
      

      【讨论】:

      • 我以前做过这个(详见altruix.wordpress.com/portfolio/project-control-center)。问题是,如果您编写大量类,最终会得到大量样板代码(接口、工厂接口及其实现)。所以我试图在不付出成本(大量样板代码)的情况下获得该设计的优势(可测试性)。
      • 在编写基于模拟的测试时采用这种方法绝对是一种危险。我在一个代码库上工作,其中大部分逻辑已经被模拟驱动重构的磨刀石磨成细粉。在这种情况下,鉴于 DataSource 已经存在,代表了一个定义明确、自包含的概念,并且已经有实现,我认为样板文件过度增长的风险很小。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-06-18
      • 2014-07-15
      • 1970-01-01
      • 2015-01-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多