2

I've been running some tests on an android application using Robolectric and it's been working well until now.

My application passes the result of getApplicationContext() to a constructor, but Robolectric should be ensuring the value of this is not null as I am using the BuildActivity() method. The error is in some configuration of my test environment, or a bug in Robolectric. The result is a NullPointerException in the constructor of Toast.

My environment:

com.android.tools.build:gradle:3.0.1
org.robolectric:robolectric:3.4.2
junit:junit:4.12

I'm trying to test our class SystemMonitorActivity, which is abstract

public abstract class SystemMonitorActivity extends BaseActivity implements OnClickListener

BaseActivity:

public class BaseActivity extends Activity implements IHandleMessageReceived, Thread.UncaughtExceptionHandler

I've created a subclass of SystemMonitorActivity so that I can instantiate it in my tests:

public class SystemMonitorActivitySubclass extends SystemMonitorActivity

Here is my mock test class to reproduce the error I am seeing:

@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class)
public class BuildActivityTest {

    @Test
    public void onCreateTest000() throws Exception {
        SystemMonitorActivitySubclass systemMonitorActivitySubclass = Robolectric.buildActivity(SystemMonitorActivitySubclass.class).create().get();
    }

}

My test output:

java.lang.NullPointerException
    at android.widget.Toast.__constructor__(Toast.java:114)
    at android.widget.Toast.<init>(Toast.java)
    at org.robolectric.shadows.ShadowToast.makeText(ShadowToast.java:38)
    at android.widget.Toast.makeText(Toast.java)
    at com.siemens.hc.poc.isf.admin.sysmon.SystemMonitorActivity.setUpDeviceOwner(SystemMonitorActivity.java:649)
    at com.siemens.hc.poc.isf.admin.sysmon.SystemMonitorActivity.onCreate(SystemMonitorActivity.java:401)
    at android.app.Activity.performCreate(Activity.java:6975)
    at org.robolectric.util.ReflectionHelpers.callInstanceMethod(ReflectionHelpers.java:232)
    at org.robolectric.android.controller.ActivityController$1.run(ActivityController.java:73)
    at org.robolectric.shadows.ShadowLooper.runPaused(ShadowLooper.java:366)
    at org.robolectric.shadows.CoreShadowsAdapter$1.runPaused(CoreShadowsAdapter.java:27)
    at org.robolectric.android.controller.ActivityController.create(ActivityController.java:70)
    at org.robolectric.android.controller.ActivityController.create(ActivityController.java:80)
    at com.siemens.hc.poc.isf.admin.sysmon.BuildActivityTest.setUp(BuildActivityTest.java:63)
    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.RunBefores.evaluate(RunBefores.java:24)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
    at org.robolectric.internal.SandboxTestRunner$2.evaluate(SandboxTestRunner.java:228)
    at org.robolectric.internal.SandboxTestRunner.runChild(SandboxTestRunner.java:110)
    at org.robolectric.internal.SandboxTestRunner.runChild(SandboxTestRunner.java:37)
    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.robolectric.internal.SandboxTestRunner$1.evaluate(SandboxTestRunner.java:64)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.runTestClass(JUnitTestClassExecuter.java:114)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.execute(JUnitTestClassExecuter.java:57)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassProcessor.processTestClass(JUnitTestClassProcessor.java:66)
    at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32)
    at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93)
    at com.sun.proxy.$Proxy1.processTestClass(Unknown Source)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:108)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:146)
    at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:128)
    at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:404)
    at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:63)
    at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:46)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:55)
    at java.lang.Thread.run(Thread.java:748)

I've made sure to include the Android resources in my tests in build.gradle:

android.testOptions.unitTests.includeAndroidResources = true

Could it be that I need to add SystemMonitorActivitySubclass to the AndroidManifest.xml somehow?


Working our way through the stack trace, the actual NPE occurs in this snippet of code:

java.lang.NullPointerException
    at android.widget.Toast.__constructor__(Toast.java:114)

public Toast(@NonNull Context context, @Nullable Looper looper) {
    mContext = context;
    mTN = new TN(context.getPackageName(), looper);
    ...

Which is called from Robolectrics ShadowToast:

at android.widget.Toast.<init>(Toast.java)
at org.robolectric.shadows.ShadowToast.makeText(ShadowToast.java:38)

@Implementation
public static Toast makeText(Context context, CharSequence text, int duration) {
    Toast toast = new Toast(context);
    ...

Context being passed to makeText is getApplicationContext(), which should be handled by Robolectric to ensure it is not null:

at android.widget.Toast.makeText(Toast.java)
at com.siemens.hc.poc.isf.admin.sysmon.SystemMonitorActivity.setUpDeviceOwner(SystemMonitorActivity.java:649)

Toast.makeText(getApplicationContext(), "Not a device owner", Toast.LENGTH_SHORT).show();

I've been debugging this for 6+ hours now and I don't feel any closer to figuring out what the issue is. Any help is much appreciated, thanks!


UPDATE: I've tried the following approach as suggested by Anton Malyshev

@Test
public void onCreateTest000() throws Exception {
    ActivityController controller = Robolectric.buildActivity(SystemMonitorActivitySubclass.class).create().start();
    Activity activity = (Activity)controller.get();
}

This has the same result;

java.lang.NullPointerException
at android.widget.Toast.__constructor__(Toast.java:114)
  • 1
    I'm well aware of what an NPE is, thanks, and I've fixed thousands previously. Robolectric *should* be ensuring that this doesn't occur, but I must have an incorrect configuration, or there is a bug in Robolectric. – Andreas Lärfors Jan 22 '18 at 15:45
  • 1
    It isn't roboelectric. `at com.siemens.hc.poc.isf.admin.sysmon.SystemMonitorActivity.setUpDeviceOwner(SystemMonitorActivity.java:649)` is the root in your code. You most likely pass null to the method – Zoe Jan 22 '18 at 15:46
  • *It isn't roboelectric.* yes, it is(or wrong usage of it) not his code ... obviously he has there only Toast.makeText.show there – Selvin Jan 22 '18 at 15:47
  • @AndreasLärfors are you sure that calling `create()` in method with @Before is valid? – Selvin Jan 22 '18 at 15:48
  • @Selvin See updated code - I get the same result calling create() in a @ Test – Andreas Lärfors Jan 22 '18 at 15:55
  • @AmitVaghela you could at least read the question before closing it as duplicate... In this case it is NOT. Obviously getApplicationContext returns null... But is should not because it is called from onCreate... And prolly it is working when application is running on device, BUT IT IS FAIL FROM TESTS. – Selvin Jan 22 '18 at 16:51
  • 2
    This is NOT a duplicate question, but I can understand that the original question title was misleading and easily interpreted as simply "another NPE". New question asked: https://stackoverflow.com/questions/48396926/robolectric-not-handling-getapplicationcontext-correctly-returns-null – Andreas Lärfors Jan 23 '18 at 08:14
  • 1
    This question should be reopen, because NPE happened here has nothing to do with NPE causes described in the "duplicate" question – Andremoniy Jan 23 '18 at 09:42
  • Thanks! Question has been re-opened! – Andreas Lärfors Jan 23 '18 at 12:31

1 Answers1

0

Seems you forgot to call .start() before .get(), so you're getting NullPointerException on accessing the context inside Toast, check the docs: http://robolectric.org/activity-lifecycle/

So I advice to change

systemMonitorActivitySubclass = Robolectric.buildActivity(SystemMonitorActivitySubclass.class).create().get();

to:

systemMonitorActivitySubclass = Robolectric.buildActivity(SystemMonitorActivitySubclass.class).create().start().get();

Probably it's better to keep ActivityController reference as well (as in their sample code).

Anton Malyshev
  • 8,686
  • 2
  • 27
  • 45
  • *Seems you forgot to call .start() before .get()* There is no need for this if you only want to test create ... *Probably it's better to keep ActivityController* this could be a good hint ... As i wrote I think the problem is calling create inside @Before method – Selvin Jan 22 '18 at 15:50
  • Thanks @AntonMalyshev but using ..start()... has the same result, see update in my question. Also note that moving everything from @ Before to @ Test does not solve the problem either. – Andreas Lärfors Jan 22 '18 at 16:17
  • Have you tried to create the activity exactly as in their docs? I mean, keeping the reference to ActivityController or adding the intent? – Anton Malyshev Jan 22 '18 at 16:20