2

I am receiving a NullPointerException when I try to create an AppCompatImageView with a mock Context in a test. Doing the same with a normal ImageView works.

This test pass:

import android.content.Context;
import android.widget.ImageView;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnitRunner;

import static junit.framework.Assert.assertNotNull;

@RunWith(MockitoJUnitRunner.class)
public class ParallaxViewTest {

    @Mock
    Context mContext;

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void initWithContext() throws Exception {
        assertNotNull(mContext);
        ImageView imageView = new ImageView(mContext);
//        AppCompatImageView imageView = new AppCompatImageView(mContext);
    }
}

This test DOES NOT pass:

import android.content.Context;
import android.support.v7.widget.AppCompatImageView;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnitRunner;

import static junit.framework.Assert.assertNotNull;

@RunWith(MockitoJUnitRunner.class)
public class ParallaxViewTest {

    @Mock
    Context mContext;

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void initWithContext() throws Exception {
        assertNotNull(mContext);
//        ImageView imageView = new ImageView(mContext);
        AppCompatImageView imageView = new AppCompatImageView(mContext);
    }
}

And this is the crash report:

java.lang.NullPointerException
    at android.support.v7.widget.ResourcesWrapper.<init>(ResourcesWrapper.java:46)
    at android.support.v7.widget.TintResources.<init>(TintResources.java:34)
    at android.support.v7.widget.TintContextWrapper.<init>(TintContextWrapper.java:100)
    at android.support.v7.widget.TintContextWrapper.wrap(TintContextWrapper.java:68)
    at android.support.v7.widget.AppCompatImageView.<init>(AppCompatImageView.java:60)
    at android.support.v7.widget.AppCompatImageView.<init>(AppCompatImageView.java:56)
    at android.support.v7.widget.AppCompatImageView.<init>(AppCompatImageView.java:52)
    at example.views.ParallaxViewTest.initWithContext(ParallaxViewTest.java:30)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.mockito.internal.runners.DefaultInternalRunner$1.run(DefaultInternalRunner.java:68)
    at org.mockito.internal.runners.DefaultInternalRunner.run(DefaultInternalRunner.java:74)
    at org.mockito.internal.runners.StrictRunner.run(StrictRunner.java:39)
    at org.mockito.junit.MockitoJUnitRunner.run(MockitoJUnitRunner.java:161)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:117)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:42)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:262)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:84)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

Here the libraries:

testCompile 'junit:junit:4.12'
testCompile 'org.mockito:mockito-core:2.7.18'

How can I solve this?


EDIT

How can I get a mock Context with resources?

This test DOES NOT pass:

    @Test
    public void initWithContext() throws Exception {
        assertNotNull(mContext); // PASS
        assertNotNull(mContext.getResources()); // DO NOT PASS
//        ImageView imageView = new ImageView(mContext);
//        AppCompatImageView imageView = new AppCompatImageView(mContext);
    }
MarcGV
  • 1,242
  • 1
  • 10
  • 33
  • Yes, I found the when().thenReturn() functionality. This is exactly was I was searching. Thanks – MarcGV Mar 23 '17 at 14:07
  • You might want to check if that DEEP stubbing might do that automatically; but maybe I am also just getting that feature wrong. Glad you got what you were looking for! – GhostCat Mar 23 '17 at 14:08
  • 1
    One final hint: you are over-doing. You are using the Mockito runner already, thus the call to ` MockitoAnnotations.initMocks(this);` isnt required at all. You only need to initMocks() manually when you are **not** using the Mockito runner for some reason. So your "setup" method is actually not required at all (so far; that might change, for example when you want to put some generic when/then clauses into the setup method; because you need them for *all* your tests) – GhostCat Mar 23 '17 at 14:09

1 Answers1

2

When you turn to the source code of the class listed in the stack trace (ResourcesWrapper), you find:

public ResourcesWrapper(Resources resources) {
  super(resources.getAssets(), resources.getDisplayMetrics(), 

And that line 46 is the one with super().

Further looking into the classes in your stack trace, you could come accross:

 private TintContextWrapper(@NonNull final Context base) {
  super(base);
  ...
  mResources = new VectorEnabledTintResources(this, base.getResources());

So, long story short, yes, you are providing a non null mock object to new AppCompatImageView() in your code. But then the code you are calling is calling methods on that mocked object. Sure, that is why you created a mock in the first place. But guess what; by default, the mocking framework will return null for any method call.

In other words: you have to understand which calls will happen on that mock; so that you can prepare the mock to return something non null too!

To be precise: I am not saying that exactly that line from TintContextWrapper() causes this NPE; I am mainly saying: when you give a mocked object into other code, you have to prepare that mock to return reasonable results on those method calls that will happen. That could very well mean that you have to create more mocks; so that something like mockedContext.getResources() does return a non-null result.

In other words: you have to

  • identify those calls that happen on that mock object
  • then you have to make sure that those calls will return non-null (for example by returning mocked objects again).

Beyond that: more likely, the real answer is to use Android specific mocking frameworks. Preparing your mocks to get them "do the right thing" could easily turn into a lot of work.

Maybe the simple answer is to use the deep stubbing from mockito, by simply writing

@Mock (answer = Answers.RETURNS_DEEP_STUBS)

But you need to read/try that; I haven't used that myself.

And given your latest: you need to configure your mock, like

when(context.getResources()).thenReturn(someOtherMock);

for example! That is the whole point of mocks: you can control what happens when methods are called!

GhostCat
  • 137,827
  • 25
  • 176
  • 248
  • You're explaining how to search a solution and I appreciate that, thanks, but I think that the answer should be easier because I am using the libraries of Android documentation for test and create a simple AppCompatImageView it shouldn't be hard. ImageView works perfectly, why AppCompatImageView doesn't? – MarcGV Mar 23 '17 at 10:41
  • I assume that I am not the only one with that problem, but following your last comment, the answer is how to set a Resources to the Context, simply. – MarcGV Mar 23 '17 at 11:51