Android methods are not mocked when using Mockito

14,454

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:

  1. Refactor your system to introduce a wrapper class that you control, which is simple enough to require little or no testing.

  2. 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.

  3. 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.

Share:
14,454
tomrozb
Author by

tomrozb

Android, Flutter, GCP, Blockchain

Updated on June 07, 2022

Comments

  • tomrozb
    tomrozb almost 2 years

    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?

  • tomrozb
    tomrozb almost 9 years
    I thought this may be the case that support libraries are not replaced like androd.jar: "At runtime, tests will be executed against a modified version of android.jar where all final modifiers have been stripped off". I'm testing class that takes Adapter in constructor, but I need to verify that notify methods are called on this adapter so the 1. solution doesn't help I think. I'm already using Roblectric but it's way too slow, that's why I'm trying to mock it. I'm familiar with PowerMock but I didn't want to add additional test dependency - it's look like I've no choice. Thanks Jeff!
  • Yair Kukielka
    Yair Kukielka about 8 years
    there is another option: instrumented tests. They are slow, but you can use Android classes in your tests. See medium.com/@yair.kukielka/…
  • Jeff Bowman
    Jeff Bowman about 8 years
    @Yair: Though instrumented tests are an effective approach to test with real implementations (on a device or in an emulator), they would be even less appropriate for using mocks as in the original question.
  • Yair Kukielka
    Yair Kukielka about 8 years
    @JeffBowman I agree, you can either use Mockito or create an instrumented test, but both would be a bit strange. I think Mockito makes UI testing a bit cumbersome sometimes.