使用MockitoJUnitRunner实例化AppCompatActivity时抛出空指针异常
问题描述
我想通过MockitoJUnitRunner模拟继承自AppCompatActivity的Activity,虽然知道Android里测试Activity应该用Instrumentation Test,但因为要测试旧代码里写在Activity内的业务逻辑,所以想写单元测试。
我的测试代码如下:
@RunWith(MockitoJUnitRunner.class) public class SampleActivityTest extends TestCase { @Test public void testOnCreate() { SampleActivity sampleActivity= new SampleActivity(); assertTrue(true); } }
执行时,SampleActivity sampleActivity= new SampleActivity();这一行抛出NullPointerException,异常堆栈如下:
java.lang.NullPointerException at androidx.arch.core.executor.DefaultTaskExecutor.isMainThread(DefaultTaskExecutor.java:77) at androidx.arch.core.executor.ArchTaskExecutor.isMainThread(ArchTaskExecutor.java:116) at androidx.lifecycle.LifecycleRegistry.enforceMainThreadIfNeeded(LifecycleRegistry.java:316) at androidx.lifecycle.LifecycleRegistry.addObserver(LifecycleRegistry.java:172) at androidx.activity.ComponentActivity.<init>(ComponentActivity.java:242) at androidx.fragment.app.FragmentActivity.<init>(FragmentActivity.java:103) at androidx.appcompat.app.AppCompatActivity.<init>(AppCompatActivity.java:94) at com.samsung.android.easysetup.appupdate.view.SampleActivity.<init>(SampleActivity.java:25) at com.samsung.android.easysetup.appupdate.view.SampleActivityTest.testOnCreate(SampleActivityTest.java:21) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) 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.mockito.internal.runners.DefaultInternalRunner$1$1.evaluate(DefaultInternalRunner.java:54) 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:99) at org.mockito.internal.runners.DefaultInternalRunner.run(DefaultInternalRunner.java:105) at org.mockito.internal.runners.StrictRunner.run(StrictRunner.java:40) at org.mockito.junit.MockitoJUnitRunner.run(MockitoJUnitRunner.java:163) at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.runTestClass(JUnitTestClassExecutor.java:110) at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:58) at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:38) at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestClassProcessor.processTestClass(AbstractJUnitTestClassProcessor.java:62) at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36) at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94) at com.sun.proxy.$Proxy5.processTestClass(Unknown Source) at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:119) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36) at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:182) at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:164) at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:414) at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64) at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56) at java.base/java.lang.Thread.run(Thread.java:834)
解决方法
从堆栈可以看出,异常是因为DefaultTaskExecutor.isMainThread()里访问了mMainHandler导致空指针——在单元测试环境中,Android的主线程Handler没有被初始化,而Activity的构造过程会触发Lifecycle相关逻辑,强制检查是否在主线程。
有几种可行的解决方式:
1. 用Mockito模拟Activity,而非直接实例化
既然是测试业务逻辑,不需要真实的Activity实例,直接用Mockito模拟:
@RunWith(MockitoJUnitRunner.class) public class SampleActivityTest extends TestCase { @Mock private SampleActivity sampleActivity; @Test public void testBusinessLogic() { // 调用要测试的业务方法,示例: when(sampleActivity.calculateSomething()).thenReturn(100); assertEquals(100, sampleActivity.calculateSomething()); } }
这种方式跳过了真实Activity的构造逻辑,直接模拟对象来测试业务方法。
2. 初始化ArchTaskExecutor的测试环境
如果必须调用真实的业务方法,可以在测试前替换ArchTaskExecutor的实现,让它不检查主线程:
@RunWith(MockitoJUnitRunner.class) public class SampleActivityTest extends TestCase { @Before public void setup() { // 替换为不检查主线程的TaskExecutor ArchTaskExecutor.getInstance().setDelegate(new TaskExecutor() { @Override public void executeOnDiskIO(Runnable runnable) { runnable.run(); } @Override public void postToMainThread(Runnable runnable) { runnable.run(); } @Override public boolean isMainThread() { return true; // 直接返回true,绕过检查 } }); } @After public void tearDown() { // 恢复原来的Executor ArchTaskExecutor.getInstance().setDelegate(null); } @Test public void testOnCreate() { // 若Context为空,可通过Mockito模拟传入 SampleActivity sampleActivity = Mockito.spy(new SampleActivity()); assertTrue(true); } }
3. 拆分业务逻辑到独立类(最优解)
长远来看,把Activity里的业务逻辑抽离到单独的ViewModel、UseCase或者普通Java类中,这样单元测试不需要依赖Android组件,测试起来更简单:
// 抽离的业务类 public class SampleBusinessLogic { public int calculateSomething() { // 原来Activity里的业务代码 return 50 + 50; } } // Activity里调用这个类 public class SampleActivity extends AppCompatActivity { private SampleBusinessLogic businessLogic; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); businessLogic = new SampleBusinessLogic(); int result = businessLogic.calculateSomething(); // 后续逻辑 } } // 单元测试直接测试业务类 public class SampleBusinessLogicTest { @Test public void testCalculateSomething() { SampleBusinessLogic logic = new SampleBusinessLogic(); assertEquals(100, logic.calculateSomething()); } }
这种方式从根本上解决了依赖Android组件的问题,也是Android开发推荐的架构模式。
内容的提问来源于stack exchange,提问作者Raj
相关产品推荐
相关产品推荐

