AndroidX纯单元测试:避免初始化MyApplication及资源使用方案
嘿,我之前也碰到过一模一样的问题——想写纯单元测试,结果每次都把整个Application拉起来,烦得不行。结合你的情况,给你整理几个可行的方案:
一、先搞定「不让MyApplication初始化」的问题
你测试时触发MyApplication.onCreate,核心原因是Robolectric或者includeAndroidResources=true会默认初始化你的主Application类。要绕开这个,有两个简单办法:
1. 给Robolectric指定一个空的测试专用Application
在测试类上加注解,告诉Robolectric别用你真实的App,用个空壳:
@RunWith(RobolectricTestRunner.class) @Config(application = TestApplication.class) public class MyClassTest { // 你的测试代码 }
然后新建一个啥都不干的TestApplication:
public class TestApplication extends Application { // 空实现,onCreate啥也不执行 }
这样Robolectric就只会初始化这个空App,完全不会碰你真实的MyApplication逻辑。
2. 直接禁用Robolectric的Application初始化(Robolectric 4.0+支持)
如果不想新建类,也可以直接在@Config里设application = null:
@RunWith(RobolectricTestRunner.class) @Config(application = null) public class MyClassTest { // 测试代码 }
不过这个方式可能会影响资源获取,更推荐第一种。
二、不用includeAndroidResources也能访问应用资源
你说testOptions.unitTests.includeAndroidResources = true会导致整个App被初始化,那换用Robolectric的资源加载能力就好——它不需要开启这个开关,就能单独加载资源:
比如你的测试可以这么写:
@RunWith(RobolectricTestRunner.class) @Config(application = TestApplication.class) public class MyClassTest { @Test public void testGetAdditionString() { // 拿到能访问资源的Context,但用的是我们的空TestApplication Context context = ApplicationProvider.getApplicationContext(); String result = MyClass.getAdditionString(context, 2, 3); // 直接断言结果和资源拼接后的内容一致 assertEquals("5" + context.getString(R.string.mystring), result); } }
这样既拿到了真实的应用资源,又不会触发真实App的初始化,完美解决你的需求。
如果连Robolectric都不想用,还有个更轻量的方案——用Mockito模拟Context的资源调用:
import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class MyClassTest { @Mock Context mockContext; @Test public void testGetAdditionString() { // 模拟getString返回你预期的资源内容 when(mockContext.getString(R.string.mystring)).thenReturn(" 是计算结果"); String result = MyClass.getAdditionString(mockContext, 2, 3); assertEquals("5 是计算结果", result); } }
这个是纯JVM单元测试,完全脱离Android环境,速度贼快,适合逻辑简单的方法测试。
三、用AndroidX测试库写原生单元测试当然可以!
AndroidX测试库完全支持纯本地单元测试,关键是要区分本地单元测试(跑在JVM上,不需要设备)和仪器化测试(跑在设备/模拟器上):
- 你当前的
testImplementation依赖都是本地单元测试用的,没问题;testInstrumentationRunner是给仪器化测试用的,本地测试可以不用管它 - 不要用
AndroidJUnit4(这是仪器化测试的Runner),改用MockitoJUnitRunner或者JUnit5的@ExtendWith(MockitoExtension.class) - 用Mockito模拟Android相关依赖(比如Context),或者用Robolectric提供轻量Android环境
比如用JUnit5+Mockito的写法:
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import static org.mockito.Mockito.when; import static org.junit.jupiter.api.Assertions.assertEquals; @ExtendWith(MockitoExtension.class) public class MyClassTest { @Mock Context mockContext; @Test void testGetAdditionString() { when(mockContext.getString(R.string.mystring)).thenReturn(" 正确"); String result = MyClass.getAdditionString(mockContext, 1, 4); assertEquals("5 正确", result); } }
这个就是纯原生单元测试,完全不会触发任何Application的初始化。
最后给你个最优选择
如果需要访问真实应用资源,又不想碰MyApplication:
用RobolectricTestRunner + 空TestApplication + ApplicationProvider获取Context
如果只需要测试业务逻辑,不想依赖Android环境:
用Mockito模拟Context,写纯JVM单元测试
内容的提问来源于stack exchange,提问作者user1202032

