问题
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.
回答1:
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.
回答2:
You can not mock final
classes and final
and/or static
methods with mockito. Note that static
nested classes are fine. This is because mockito subclasses (I'm not 100% sure this is the exact operation, it uses CGLIB to generate classes) objects, but isn't allowed to override final methods or extend the final classes. For static
methods no overriding is ever possible.
In your code you probably are trying to call a final class or method. It is hard to tell which class causes the problem, from your NullPointer
stackstrace you should suspect the first object on it that you have mocked (going up starting from the testcase method). The method on the mock should not be calling any other methods (expect internal to mockito), so probably that is final, because you is does not seem to call a 'mocked' method.
In your case the stacktrace is not complete (as your testcase is not on it). In a quick look on the riak framework I couldn't find the method take a look at FetchValue$Response.getValue
.
Also note the following. From the snippet you posted, I can not tell what you are testing in your testcase. All objects you create are mocks. Normally you have 1 (or a few) real classes that you are testing. The other classes (that interact with your classes under test) you mock, to be able to simulate complex behavior.
回答3:
Follow up: Thanks @gontard I was able to find this:
<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>
That has the fixes included.
Unfortunately, if you use both Fetch and MultiFetch (likely), you're up a creek.
MultiFetch.Response is a final class (so you can use mockito, you need to use PowerMock) FetchValue.Response has the issues you outlined, and can only be fixed with the beta mockito, not available with powermock yet...
Update, I figured out how to use both mockito & powermock together (until powermock upgrades):
<!-- 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>
来源:https://stackoverflow.com/questions/28442495/how-to-mock-riak-java-client