如何对使用SimpleJdbcCall的类进行单元测试

时间:2015-10-19 22:45:00

标签: java unit-testing mocking mockito spring-jdbc

我有一个看起来像这样的课程:

public class MyClass {

    private final SimpleJdbcCall simpleJdbcCall;

    public MyClass(final DataSource dataSource) {
        this(new JdbcTemplate(dataSource));
    }

    public MyClass(final JdbcTemplate template) {
        simpleJdbcCall = new SimpleJdbcCall(template)
            .withoutProcedureColumnMetaDataAccess()
            .withCatalogName("MY_ORACLE_PACKAGE")
            .withFunctionName("GET_VALUE")
            .withReturnValue()
            .declareParameters(
                new SqlOutParameter("RESULT", Types.VARCHAR))
            .declareParameters(
                new SqlParameter("P_VAR1_NAME", Types.VARCHAR))
            .declareParameters(
                new SqlParameter("P_VAR2_NAME", Types.VARCHAR))
            .useInParameterNames("P_VAR1_NAME", "P_VAR2_NAME");
    }

    private String getValue(final String input) {
        final SqlParameterSource params = new MapSqlParameterSource()
            .addValue("P_VAR1_NAME", input, Types.VARCHAR)
            .addValue("P_VAR2_NAME", null, Types.VARCHAR);
        return simpleJdbcCall.executeFunction(String.class, params);
    }
}

它按预期工作,但我想为它编写单元测试,这让我发疯。我已经尝试过模拟JdbcTemplate(Mockito),但这会导致模拟连接,元数据等,并且我对可调用语句工厂发挥作用的时间感到迷茫。

我想我可以编写它,以便SimpleJdbcCall作为参数传递给新的构造函数,然后模拟它,但这感觉很乱。我希望测试不会影响课程,除非是为了改进它。

我想继续使用这个SimpleJdbcCall API。它为我编写SQL,所以我不必混合SQL和Java,但我也非常想测试这个东西,而不必编写1000行代码。任何人都可以看到一个很好的方法来测试这个吗?

5 个答案:

答案 0 :(得分:1)

我也不想将15个不同的SimpleJdbcCalls注入到我的存储库中,所以我咬紧牙关,并将其添加到我的测试设置方法中:

DatabaseMetaData metaData = mock(DatabaseMetaData.class);
Connection con = mock(Connection.class);
when(con.getMetaData()).thenReturn(metaData);
DataSource ds = mock(DataSource.class);
when(ds.getConnection()).thenReturn(con);
jdbcTemplate = mock(JdbcTemplate.class);
when(jdbcTemplate.getDataSource()).thenReturn(ds);

答案 1 :(得分:0)

我肯定会采用添加构造函数的方法来允许SimpleJdbcCall直接注入。

MyClass(SimpleJdbcCall simpleJdbcCall) {
  this.simpleJdbcCall = simpleJdbcCall;
}

(可能从当前调用new的那个构造函数中调用该构造函数。)

这不是" hackish",它只是Dependency Injection。我认为让课堂可测试而不需要测试SimpleJdbcCall的工作方式是一个明显的改进。

在构造函数中调用new会使测试更加困难,因为它是与实例化的类的紧密静态耦合。

我发现这个主题Miško Hevery's blog post非常有趣。

答案 2 :(得分:0)

我的第一个建议是单元测试它;编写集成测试,该测试实际执行Oracle数据库中存储的函数(但回滚事务)。

否则,您可以使用PowerMockito或JMockit模拟SimpleJdbcCall类,其中包含测试代码。

使用JMockit进行的示例测试:

@Mocked DataSource ds;
@Mocked SimpleJdbcCall dbCall;

@Test
public void verifyDbCall() {
    String value = new MyClass(ds).getValue("some input");

    // assert on value

    new Verifications() {{
        SqlParameterSource params;
        dbCall.executeFunction(String.class, params = withCapture());
        // JUnit asserts on `params`
    }};
}

答案 3 :(得分:0)

添加额外的构造函数:

/*test*/ MyClass(final SimpleJdbcCall call) {
    simpleJdbcCall = call
        .withoutProcedureColumnMetaDataAccess()
        .withCatalogName("MY_ORACLE_PACKAGE")
        .withFunctionName("GET_VALUE")
        .withReturnValue()
        .declareParameters(
            new SqlOutParameter("RESULT", Types.VARCHAR))
        .declareParameters(
            new SqlParameter("P_VAR1_NAME", Types.VARCHAR))
        .declareParameters(
            new SqlParameter("P_VAR2_NAME", Types.VARCHAR))
        .useInParameterNames("P_VAR1_NAME", "P_VAR2_NAME");
}

这个包是私有的,所以同一个包(= tests)中的其他类可以调用它。这样,测试可以创建一个覆盖executeFunction()的实例。您可以在方法中返回虚假结果或测试对象的状态。

这意味着您的代码仍然配置对象;测试只是传递了一个“POJO”,被测试的代码填满了。

这样,您就不必编写大量代码 - 默认实现可以完成大部分工作。

或者,允许使用接口SimpleJdbcCallOperations调用构造函数,这意味着您需要一个强大的模拟框架或编写大量的样板代码。

其他替代方法:使用模拟JDBC驱动程序。这些通常很难设置,导致虚假的测试失败,当测试失败时,你通常不知道为什么,......

或内存数据库。它们带来了一大堆问题(您需要加载制造和维护所需的测试数据)。

这就是为什么我尽量避免通过JDBC层进行往返的原因。假设JDBC和数据库正常工作 - 其他人已经测试了这段代码。如果你再次这样做,那你只是在浪费时间。

相关:

答案 4 :(得分:0)

我是使用http://www.jmock.org/

完成的

XML配置 -                   

<bean id="simpleJDBCCall" class="org.springframework.jdbc.core.simple.SimpleJdbcCall">
    <property name="jdbcTemplate" ref="jdbcTemplate" />
</bean>

Java文件 -

@Autowired
private SimpleJdbcCall jdbcCall;

测试类 -

simpleJDBCCall = mockingContext.mock(SimpleJdbcCall.class);
mockingContext.checking(new Expectations() {
        { 
            oneOf(simpleJDBCCall).withSchemaName("test");
            will(returnValue(simpleJDBCCall));
            oneOf(simpleJDBCCall).withCatalogName("test");
            will(returnValue(simpleJDBCCall));
            oneOf(simpleJDBCCall).withProcedureName(ProcedureNames.TEST);
            will(returnValue(simpleJDBCCall));
            oneOf(simpleJDBCCall).execute(5);
            will(returnValue(testMap));
        }