JUnit与Mock集成:JUnit4/5中的Mock使用
前面的章节我们聊了Mock的基础用法。但说实话,真正在AOSP项目里跑测试,很少孤立地用Mockito。你得把它和JUnit框架捏合到一起。JUnit4和JUnit5虽然都能跑,但集成方式差异不小。
我记得去年帮团队迁移测试框架时,就踩了不少坑。就拿初始化Mock对象来说,JUnit4用@RunWith,JUnit5换成了@ExtendWith。如果不小心混用,测试就跑不起来。
JUnit4的Mock集成:经典但受限
JUnit4时代,集成Mock主要靠两种方式。我个人习惯用@RunWith,因为它最简洁。
看个具体例子:
@RunWith(MockitoJUnitRunner.class)
public class AudioPolicyServiceTest {
@Mock
private AudioFlinger audioFlinger;
@Mock
private AudioPolicyEffects audioPolicyEffects;
@InjectMocks
private AudioPolicyService service;
@Test
public void testSetDeviceConnectionState() {
when(audioFlinger.getSampleRate(anyInt())).thenReturn(48000);
int result = service.setDeviceConnectionState(...);
assertEquals(AudioSystem.SUCCESS, result);
}
}
但问题来了。如果你还想用Spring的@RunWith(SpringRunner.class)做集成测试,那就冲突了。我曾经遇到一个AOSP伙伴,想把Mockito和Robolectric混用,折腾了半天只能用MockitoAnnotations.initMocks(this)手工初始化。
@RunWith(RobolectricTestRunner.class)
public class MediaPlayerTest {
@Mock
private MediaPlayerService service;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testPlay() {
// 这里可以愉快地使用mock了
}
}
这种方式灵活,但@Before方法里必须显式调用initMocks。少写了?Mock对象全为null,NPE直接找上门。
JUnit5的优雅集成:Extension来了
JUnit5最大的改进之一,就是引入了Extension模型。说白了,它把Runner拆成了可组合的扩展点。你再也不用为Runner冲突发愁了。
为什么我推荐JUnit5?你想想看,AOSP里很多测试需要组合多个扩展,比如Mockito + TemporaryFolder + Spring。JUnit4搞不定的事,JUnit5轻松解决。
最常见的写法:
@ExtendWith(MockitoExtension.class)
public class CameraHalTest {
@Mock
private CameraDevice cameraDevice;
@Mock
private SurfaceTexture surfaceTexture;
@InjectMocks
private Camera3Device device;
@Test
void testConfigureStreams() {
when(cameraDevice.createCaptureSession(any())).thenReturn(...);
// 测试逻辑
}
}
如果不方便用@ExtendWith,也有手工方案:
class AudioFlingerTest {
@Mock
private AudioThread audioThread;
private MockitoExtension extension;
@BeforeEach
void setup() {
MockitoAnnotations.openMocks(this);
}
@AfterEach
void cleanup() {
MockitoAnnotations.openMocks(this).close();
}
}
实战对比:JUnit4 vs JUnit5
直接看表格对比,一目了然:
| 维度 | JUnit4 | JUnit5 |
|---|---|---|
| 初始化方式 | @RunWith(MockitoJUnitRunner.class) | @ExtendWith(MockitoExtension.class) |
| 手工初始化 | MockitoAnnotations.initMocks(this) | MockitoAnnotations.openMocks(this) |
| 扩展组合 | 不支持(单一Runner) | 支持(可叠加多个@ExtendWith) |
| 生命周期回调 | @Before/@After(固定) | @BeforeEach/@AfterEach(灵活) |
| 推荐场景 | 小型单元测试,无其他框架依赖 | AOSP组件测试,混合框架测试 |
避坑指南:我踩过的三个坑
- Runner冲突:JUnit4下,一个
@RunWith只能有一个。如果你需要Mockito + Spring,只能用@Before去初始化。我个人建议,新项目直接上JUnit5。 - 生命周期不匹配:我曾经在JUnit5里误用了
@Before(JUnit4的注解),结果初始化方法根本没执行。排查了半天。记住:JUnit5用@BeforeEach和@AfterEach。 - Mock对象泄漏:JUnit5的
openMocks不调用close,Mock对象会保留状态。尤其你复用测试类实例时,问题更明显。养成习惯:在@AfterEach里加一行MockitoAnnotations.openMocks(this).close()。
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'
testImplementation 'org.mockito:mockito-junit-jupiter:5.4.0'
嗯,到这里你应该能理清JUnit4和JUnit5的Mock集成差异了。说白了,JUnit5更灵活、更强大,尤其是多扩展组合的能力,在AOSP这种复杂项目里特别实用。我在做多媒体组件测试时,同时用MockitoExtension和TemporaryFolderExtension,代码干净很多。
关于代码示例,我建议你复制到IDE里跑一遍。看到测试绿灯亮起,你自然就理解了。下个知识点会深入Mock的Stubbing与验证,但先把集成环境搞稳,才是王道。