Как издеваться над DriverManager.getConnection(...)?

У меня есть класс, который подключается к базе данных 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();
        }
    }
...
}

Я хочу написать unit test, который проверяет, что в методе 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 возвращает реальное соединение.

Как я могу его исправить (make DriverManager return mock в тесте)?

Здесь 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>

Ответ 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 вместо вашего собственного класса factory

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