Mrtn Mrtn - 7 months ago 58
Java Question

How to mock riak java client?

I'm trying to unit test code that uses com.basho.riak:riak-client:2.0.0. I mocked all riak client classes and was hoping to get a useless but working test. However, this fails with a null pointer:

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)


My test looks like this:

@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
}


How do you unit test code that uses the riak client? Eventually I would like to ensure that the expected type/bucket/key combination is used and that the expected RiakCommand is run.

EDIT: I dug more into the FetchValue class and found this structure:

FetchValue

- is
public final


FetchValue.Response


- is
public static
,

- has a package-private constructor
Response(Init<?> builder)


FetchValue.Response.Init<T>
is:

-
protected static abstract class Init<T extends Init<T>> extends KvResponseBase.Init<T>


And there is
FetchValue.Response.Builder
:

static class Builder extends Init<Builder>


- with build() that:
return new Response(this);


I assume that Mockito gets lost somewhere among the inner classes and my call ends up in
KvResponseBase.convertValues
, where the NP is thrown.
KvResponseBase.convertValues
assumes a
List<RiakObject>
of values and I see no sane way of assigning it.

Answer

I have investigate a bit your case. I have reduce your example to this simple 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());
    }
}

which throws this error:

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)

After some digging, i think i have identified the problem. It is that you are trying to stub a public method which is inherited from a package (visibility) class:

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

It seems that Mockito fails to stub this method so the real one is invoked and a NullPointerException is thrown (due to an access of a null member: values). One important thing to note is that if this function invocation not fails, Mockito would show a proper error:

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.

I guess it is a Mockito bug or limitation so i have open an issue in the Mockito tracker where i have reproduce your case with simple classes.


UPDATE

The issue i opened is in fact a duplicate of an existing one. This issue will not be fixed but a workaround exists. You may use the Bytebuddy mockmaker instead of the cglib one. Explanations could be found here.