【问题标题】:How to mock riak java client?如何模拟 riak java 客户端?
【发布时间】:2015-02-10 21:48:29
【问题描述】:

我正在尝试对使用 com.basho.riak:riak-client:2.0.0 的代码进行单元测试。我嘲笑了所有 riak 客户端类,并希望得到一个无用但有效的测试。但是,这会因空指针而失败:

java.lang.NullPointerException
  at com.basho.riak.client.api.commands.kv.KvResponseBase.convertValues(KvResponseBase.java:243)
  at com.basho.riak.client.api.commands.kv.KvResponseBase.getValue(KvResponseBase.java:150)
  at com.basho.riak.client.api.commands.kv.FetchValue$Response.getValue(FetchValue.java:171)

我的测试如下所示:

    @Test public void test() {         
        RiakClient riakClient = mock(RiakClient.class);

        @SuppressWarnings("unchecked")
        RiakCommand<FetchValue.Response, Location> riakCommand = (RiakCommand<FetchValue.Response, Location>) mock(RiakCommand.class);

        Response response = mock(Response.class);
        when(riakClient.execute(riakCommand)).thenReturn(response);
        Response returnedResponse = riakClient.execute(riakCommand);

        when(response.getValue(Object.class)).thenReturn(new Object());
        MyPojo myData = returnedResponse.getValue(MyPojo.class);
        // Make assertions
    }

如何对使用 riak 客户端的代码进行单元测试?最终,我想确保使用预期的类型/存储桶/键组合并运行预期的 RiakCommand。

编辑:我深入研究了 FetchValue 类并发现了这个结构:
FetchValue - 是public final

FetchValue.Response
- 是public static,
- 有一个包私有构造函数Response(Init&lt;?&gt; builder)

FetchValue.Response.Init&lt;T&gt; 是:
- protected static abstract class Init&lt;T extends Init&lt;T&gt;&gt; extends KvResponseBase.Init&lt;T&gt;

还有FetchValue.Response.Builder:
static class Builder extends Init&lt;Builder&gt;
- 使用 build() :return new Response(this);

我假设 Mockito 在内部类中的某个地方迷路了,我的电话最终在KvResponseBase.convertValues 中结束,NP 被抛出。 KvResponseBase.convertValues 假定有 List&lt;RiakObject&gt; 的值,但我认为没有合理的分配方法。

【问题讨论】:

    标签: java junit mockito riak


    【解决方案1】:

    我对您的情况进行了一些调查。我已将您的示例简化为这个简单的 SSCCE:

    import static org.mockito.BDDMockito.given;
    import static org.mockito.Mockito.mock;
    import org.junit.Test;
    import com.basho.riak.client.api.commands.kv.FetchValue.Response;
    
    public class RiakTest {
        @Test
        public void test() throws Exception {
            Response response = mock(Response.class);
            given(response.getValue(Object.class)).willReturn(new Object());
        }
    }
    

    引发此错误:

    java.lang.NullPointerException
    at com.basho.riak.client.api.commands.kv.KvResponseBase.convertValues(KvResponseBase.java:243)
    at com.basho.riak.client.api.commands.kv.KvResponseBase.getValue(KvResponseBase.java:150)
    at com.basho.riak.client.api.commands.kv.FetchValue$Response.getValue(FetchValue.java:171)
    at RiakTest.test(RiakTest.java:12)
    

    经过一番挖掘,我想我已经确定了问题所在。这是您试图存根从包(可见性)类继承的公共方法

    abstract class KvResponseBase {
        public <T> T getValue(Class<T> clazz) {
        }
    }
    

    似乎 Mockito 未能存根此方法,因此调用了真正的方法并抛出了 NullPointerException(由于访问了一个空成员:values)。 需要注意的重要一点是,如果此函数调用没有失败,Mockito 将显示正确的错误:

    org.mockito.exceptions.misusing.MissingMethodInvocationException: 
        when() requires an argument which has to be 'a method call on a mock'.
        For example:
            when(mock.getArticles()).thenReturn(articles);
    
        Also, this error might show up because:
        1. you stub either of: final/private/equals()/hashCode() methods.
           Those methods *cannot* be stubbed/verified.
           Mocking methods declared on non-public parent classes is not supported.
        2. inside when() you don't call method on mock but on some other object.
    

    我猜这是一个 Mockito 错误或限制,所以我在 Mockito tracker 中打开了一个问题,我用简单的类重现了你的案例。


    更新

    The issue i opened 实际上是existing one 的副本。此问题不会得到解决,但存在解决方法。您可以使用Bytebuddy mockmaker 而不是 cglib 之一。 Explanations 可以在这里找到。

    【讨论】:

    • 已接受。你在 Mockito 周围的发现确实令人印象深刻。您帮助我意识到 Mockito 并不是完成这项任务的完美工具。既然您已经盯着 riakClient 的代码,您对如何测试使用它的代码有什么建议吗?谢谢!
    • @Mrtn 很高兴为您提供帮助。我认为您应该提出一个新问题,公开您要测试的内容。我认为您将获得更广泛的帮助,因为它与 mockito 无关,而是与一般的(单元)测试有关。即使我看了一下 Riak 的一些课程,我也是专家;)
    【解决方案2】:

    您不能使用 mockito 模拟 final 类和 final 和/或 static 方法。请注意,static 嵌套类很好。这是因为 mockito 子类(我不是 100% 确定这是确切的操作,它使用 CGLIB 生成类)对象,但不允许覆盖最终方法或扩展最终类。对于 static 方法,永远不可能覆盖。

    在您的代码中,您可能正在尝试调用最终类或方法。很难判断哪个类导致了问题,从您的NullPointer stackstrace 中,您应该怀疑您已模拟的第一个对象(从 testcase 方法开始)。 mock 上的方法不应该调用任何其他方法(期望 mockito 内部的方法),所以这可能是最终的,因为您似乎没有调用“mocked”方法。

    在您的情况下,堆栈跟踪不完整(因为您的测试用例不在上面)。快速浏览一下 riak 框架,我找不到方法,请查看 FetchValue$Response.getValue

    还要注意以下几点。从您发布的 sn-p 中,我无法判断您在测试用例中测试的是什么。您创建的所有对象都是模拟。通常你有 1 个(或几个)你正在测试的真实类。您模拟的其他类(与您的被测类交互)以便能够模拟复杂的行为。

    【讨论】:

    • 我同意你的大部分回答,尤其是最后一段:本次测试的目标是什么?但我不得不说你的诊断是错误的,我已经对其项目进行了基本的升级,并且类/方法不是最终的,方法也不是静态的。我还没有发现问题。
    • 我同意,这个测试毫无意义。我的宏伟计划是断言我的应用程序将预期的参数传递给 RiakClient。模拟和参数匹配似乎是要走的路。现在,感谢@gontard,我们知道了更多,我需要找到另一个解决方案。感谢您的关注和时间!
    【解决方案3】:

    跟进: 谢谢@gontard,我找到了这个:

    <dependency>
      <!-- We need this fix: https://github.com/mockito/mockito/pull/171 to use mockito with Riak -->
    <!--http://stackoverflow.com/questions/28442495/how-to-mock-riak-java-client#28474106-->
      <groupId>org.mockito</groupId>
      <artifactId>mockito-core</artifactId>
      <version>2.0.52-beta</version>
      <scope>test</scope>
      </dependency>
    

    其中包含修复程序。

    不幸的是,如果您同时使用 Fetch 和 MultiFetch(很可能),那么您将陷入困境。

    MultiFetch.Response 是 final 类(所以可以使用 mockito,需要使用 PowerMock) FetchValue.Response 有您列出的问题,只能使用 beta 版 mockito 修复,powermock 尚不可用...

    更新,我想出了如何同时使用 mockito 和 powermock(直到 powermock 升级):

    <!-- We need this to mock Multi-Fetch responses from Riak, which are final -->
    <!-- However, we need the beta version of mockito due to bugs (see below),
    so we _cannot_ use the mockito api provided by powermock, do _not_ include _powermock-api-mockito, it'll mess stuff up -->
    <dependency>
      <groupId>org.powermock</groupId>
      <artifactId>powermock-module-junit4</artifactId>
      <version>1.6.4</version>
      <scope>test</scope>
    </dependency>
    <!--If we don't include this, we get: -->
    <!--java.lang.IllegalStateException:
    Extension API internal error: org.powermock.api.extension.proxyframework.ProxyFrameworkImpl could not be located in classpath.-->
    <!-- it looks like this is due to some discrepancy in packaging with mockito 2, this may be fixed in Fall 2016:
    https://groups.google.com/forum/#!topic/powermock/cE4T40Xa_wc -->
    <dependency>
      <groupId>org.powermock</groupId>
      <artifactId>powermock-api-easymock</artifactId>
      <version>1.6.4</version>
    </dependency>
    
    
    <!-- We need this fix: https://github.com/mockito/mockito/pull/171 to use mockito with Riak -->
    <!--http://stackoverflow.com/questions/28442495/how-to-mock-riak-java-client#28474106-->
    <dependency>
      <groupId>org.mockito</groupId>
      <artifactId>mockito-core</artifactId>
      <version>2.0.52-beta</version>
      <scope>test</scope>
      </dependency>
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-06
      • 2014-07-23
      • 2022-10-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多