I'm writing a test which purpose is to test some methods in subclass of Adapter
class (used by RecyclerView
). To mock adapter behavior I've chosen Mockito library.
Here's a simple test:
import android.support.v7.widget.RecyclerView.Adapter;
public class TestAdapter {
@Test
public void testAdapter() throws Exception {
Adapter adapter = Mockito.mock(Adapter.class);
adapter.notifyDataSetChanged();
}
}
Nothing should happen but NullPointerException
is thrown instead.
java.lang.NullPointerException
at android.support.v7.widget.RecyclerView$Adapter.notifyDataSetChanged(RecyclerView.java:5380)
at pl.toro.test.adapter.TestAdapter.testAdapter(TestAdapter.java:38)
...
I've also tried with spy
instead of mock
@Test
public void testAdapterWithDoNothing() throws Exception {
Adapter adapter = Mockito.spy(new Adapter() {...});
doNothing().when(adapter).notifyDataSetChanged();
}
But this fails at second line (notice this is just a mock setup)
java.lang.NullPointerException
at android.support.v7.widget.RecyclerView$AdapterDataObservable.notifyChanged(RecyclerView.java:8946)
at android.support.v7.widget.RecyclerView$Adapter.notifyDataSetChanged(RecyclerView.java:5380)
at pl.toro.test.adapter.TestAdapter.testAdapterWithDoNothing(TestAdapter.java:59)
...
Is it possible to mock support classes with Mockito in unit tests?
The Mockito. mock() method allows us to create a mock object of a class or an interface. We can then use the mock to stub return values for its methods and verify if they were called. We don't need to do anything else to this method before we can use it.
To create a mock object for an Android dependency, add the @Mock annotation before the field declaration. To stub the behavior of the dependency, you can specify a condition and return value when the condition is met by using the when() and thenReturn() methods.
In Mockito, we mock any class using @Mock annotation. By Mocking any class, we are creating an mock object of that speicifc class. In the above code, Operators is mocked to provide dependency for Calculator class. Here, to perform test we need to annotate the function with @Test.
For Mockito, there is no direct support to mock private and static methods. In order to test private methods, you will need to refactor the code to change the access to protected (or package) and you will have to avoid static/final methods.
Android support classes obey the same rules as the rest of Java regarding what can or can't be mocked; unfortunately, the method you're referring to (notifyDataSetChanged) is final. You may also consider this a hazard of mocking types you don't own, as the final
semantics affect whether the class can be mocked.
Internally, Mockito works by creating a "subclass" (actually a proxy implementation) of the class you're mocking. Because final
method resolution can happen at compile time, Java skips the polymorphic method lookup and directly invokes your method. This is slightly faster in production, but without the method lookup Mockito loses its only way to redirect your call.
This is a common occurrence across the Android SDK (recently solved with a modified version of android.jar which has the final
modifiers stripped off) and support libraries. You might consider one of the following resolutions:
Refactor your system to introduce a wrapper class that you control, which is simple enough to require little or no testing.
Use Robolectric, which uses a special Android-SDK-targeted JVM classloader that can rewrite calls into mockable equivalents (and also parses and supplies Android resources for testing without an emulator). Robolectric provides enough Android-specific "shadows" (replacement implementations) that you can usually avoid writing your own, but also allows for custom shadows for cases like support libraries.
Use PowerMock, which extends Mockito or EasyMock using a special classloader that also rewrites the bytecode of your system under test. Unlike Robolectric, it is not Android-targeted, making it a more general-purpose solution but generally requiring more setup.
PowerMock is an extension of Mockito, so it provides strictly more functionality, but personally I try to refactor around any problem that would require it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With