【问题标题】:Matching JdbcTemplate update method with Mockito将 JdbcTemplate 更新方法与 Mockito 匹配
【发布时间】:2013-07-17 08:40:26
【问题描述】:

我正在尝试匹配我在 Dao 课程中使用的这种方法。但是我总是得到以下异常,表明没有对该方法进行调用。

要么方法不匹配,要么我做错了。

String pSql = "SELECT * FROM employee";
Object[] pArgs = new Object[] {""};
int[] pArgTypes =  new int[] {};

/* Return 1 when the call to update() is made indicating a successful database update */
when(mJdbcTemplate.update(anyString(), aryEq(pArgs), aryEq(pArgTypes))).thenReturn(1);

这是异常的堆栈跟踪:

Wanted but not invoked:
jdbcTemplate.update(<any>, <any>, <any>);
-> at com.test.GenericDaoJdbcImplTest$WhenInsertUpdateDeleteIsCalledWith.successfulUpdateShouldReturnTrue(GenericDaoJdbcImplTest.java:197)

However, there were other interactions with this mock:
-> at com.test.GenericDaoJdbcImplTest.insertUpdateDelete(GenericDaoJdbcImpl.java:121)

    at org.mockito.exceptions.Reporter.wantedButNotInvoked(Reporter.java:269)
    at org.mockito.internal.verification.checkers.MissingInvocationChecker.check(MissingInvocationChecker.java:42)
    at org.mockito.internal.verification.Times.verify(Times.java:36)
    at org.mockito.internal.verification.MockAwareVerificationMode.verify(MockAwareVerificationMode.java:21)
    at org.mockito.internal.MockHandler.handle(MockHandler.java:80)
    at org.mockito.internal.InvocationNotifierHandler.handle(InvocationNotifierHandler.java:36)
    at org.mockito.internal.creation.MethodInterceptorFilter.intercept(MethodInterceptorFilter.java:48)
    at org.springframework.jdbc.core.JdbcTemplate$$EnhancerByMockitoWithCGLIB$$92326890.update(<generated>)
    at com.test.GenericDaoJdbcImplTestTest$WhenInsertUpdateDeleteIsCalledWith.successfulUpdateShouldReturnTrue(GenericDaoJdbcImplTest.java:197)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

我的GenericDaoJdbcImplTest 是一个抽象类。这是我正在测试的课程。

public abstract class GenericDaoJdbcImpl<MODEL, PRIMARYKEY extends Serializable>
        implements GenericJdbcDao<MODEL, PRIMARYKEY> {

    @Autowired
    @Qualifier(value = "jdbcTemplate")
    private JdbcTemplate mJdbcTemplate;

    private Class<MODEL> mType;

    public JdbcTemplate getJdbcTemplate() {
        return mJdbcTemplate;
    }

    public void setJdbcTemplate(final JdbcTemplate pJdbcTemplate) {
        this.mJdbcTemplate = pJdbcTemplate;
    }

    public GenericDaoJdbcImpl(final Class<MODEL> pType) {
        this.mType = pType;
    }

    public abstract MODEL add(final MODEL mModel);

    public abstract MODEL modify(final MODEL mModel);

    public abstract MODEL read(PRIMARYKEY pId);

    public abstract List<MODEL> list();

    public abstract void delete(PRIMARYKEY pId);

    @Override
    public boolean insertUpdateDelete(final String pSql, final Object[] pArgs,
            final int[] pArgTypes) {

        Assert.hasLength(pSql, "No SQL provided to execute");
        Assert.notNull(pArgs, "No data provided to insert/update/delete");
        Assert.notNull(pArgTypes, "No data types provided for");
        Assert.isTrue(pArgs.length == pArgTypes.length, "Mis-match in data and data type count");

        return (mJdbcTemplate.update(pSql, pArgs, pArgTypes) > 0);
    }
}

【问题讨论】:

    标签: java junit junit4 mockito


    【解决方案1】:

    您必须在 when 调用中转换参数。否则参数将不明确,编译器无法将其解析为特定的update 方法。

    所以:

    Mockito.when(jdbcTemplate.update((String)Mockito.anyString(), (Object[])Mockito.anyVararg())).thenReturn(var);
    

    应该可以解决您的问题。至少对我有用。

    【讨论】:

    • 谢谢保罗。在过去的两天里,我一直因为这个而头疼。
    【解决方案2】:

    尝试使用ArgumentCaptor,这些条件往往限制较少,并允许在调用后验证复杂的参数。

    @Captor
    ArgumentCaptor<Object[]> objCap;
    @Captor
    ArgumentCaptor<int[]> intCap;
    
    when(mJdbcTemplate.update(anyString(), objCap.capture(), intCap.capture())).thenReturn(1);
    

    【讨论】:

    • 啊。为什么我没有想到这些。让我试试那些。谢谢。
    • 如果jdbcTemplate.update(&lt;any&gt;, &lt;any&gt;, &lt;any&gt;) 没有被调用,正如堆栈跟踪所暗示的那样,那么使用ArgumentCaptor 不会有任何帮助。
    【解决方案3】:

    不要模拟你不拥有的类型!这是大原则之一。模拟这些类型对测试的负面影响不止一种方式:

    1. 它使您的测试变得复杂,可能使其难以制作、阅读、理解或重构
    2. 您必须了解如何使用此类型。
    3. 它增强了测试与此实现、版本等的耦合
    4. 它给你一种错误的安全感,因为你已经模拟了行为,所以测试通过了,但真正的代码可能已经改变了行为或看到新版本的新行为,然后你会看到繁荣在服务器上!

    在您的情况下,您正在系统边界测试某些东西,因此经验法则是在处理数据库等时编写集成测试并编写单元测试 strong> 用于您的业务代码。

    我强烈推荐Growing Object Oriented Software - Guided by tests 这本书。我认为这可能是帮助使用 TDD 编写优秀软件的最有用的书之一。此外,它是由第一个模拟框架的作者 Steve FreemanNat Pryce 编写的。

    【讨论】:

    • 好的。但是,如果 JdbcTemplate 被替换为单独的商务舱会怎样。我的意思是我想知道为什么匹配没有发生?该代码检测到单独的方法调用是在类似的方法上进行的,但它与该方法不匹配。为什么会这样?
    • 使用模拟的主要原因之一是模拟您不拥有的类,这些类可能具有难以执行每条路径的复杂代码。这允许您执行 API 指定的所有选项。单元测试的重点是您单独测试组件。这并不意味着与您拥有的一切隔离开来。另外,我不同意你的说法,即这会使测试变得更加困难,我发现设置一个模拟来做 X 比设置驱动类做 X 的所有 15 个条件更容易理解。
    • 我同意@JohnB。我不知道原理;不是全部。但是单元测试的全部目的是单独测试类。而且如果我不模拟出JdbcTemplate,那么我必须设置一个虚拟数据库或使用hsqldb来运行我的单元测试,这本质上违反了“单元”测试的基本原则。
    • @divinedragon 是的,我建议这类测试使用集成测试,而不是单元测试。从长远来看,当我尝试模拟由于我引用的原因而没有完全拥有的类型时,我已经看到对项目造成的伤害更大。测试隔离也不是一个有效的论点,因为隔离不必适应正在完成的测试,对于单元测试,您希望为协作者隔离不需要的行为,对于集成测试,您希望在更高级别。
    • @JohnB 这条原则并不是一条硬线,但如果经常忽略它,您将获得设计不佳的代码,并且测试、业务和依赖项之间存在紧密耦合。我宁愿确保外部库/系统的整个行为是正确的,而不是确保交互与代码中编写的内容相匹配,而不是将时间集中在这上面。关于fixture这是一个不同的话题,集成测试并不总是需要15个条件。如果您在 Google 上搜索 don't mock type you didn't own,您会发现一些关于尝试过但失败的人的故事,并且在 mockito 邮件列表中也有很多。
    猜你喜欢
    • 1970-01-01
    • 2015-06-22
    • 1970-01-01
    • 2023-03-17
    • 1970-01-01
    • 2022-12-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多