如何模拟riak java客户端?

时间:2015-02-10 21:48:29

标签: java junit mockito riak

我正在尝试单元测试使用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<?> builder)

FetchValue.Response.Init<T>是:
- protected static abstract class Init<T extends Init<T>> extends KvResponseBase.Init<T>

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

我认为Mockito迷失在内部类中的某个地方,我的调用最终会在KvResponseBase.convertValues中,NP被抛出。 KvResponseBase.convertValues假定值为List<RiakObject>,我认为没有合理的分配方式。

3 个答案:

答案 0 :(得分: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模拟器而不是cglib模拟器。 Explanations可以在这里找到。

答案 1 :(得分:0)

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

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

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

另请注意以下内容。从您发布的代码片段中,我无法分辨您在测试用例中测试的内容。您创建的所有对象都是模拟。通常,您正在测试1个(或几个)实际类。您模拟的其他类(与您正在测试的类交互),以便能够模拟复杂的行为。

答案 2 :(得分:0)

跟进:   谢谢@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是一个最终类(所以你可以使用mockito,你需要使用PowerMock) FetchValue.Response有您列出的问题,只能通过beta mockito修复,但还没有powermock ...

更新,我想出了如何同时使用mockito和amp; 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>