如何为继承MediaPlayer的Kotlin类编写单元测试并解决空指针异常
问题原因
本地单元测试环境下,Android SDK提供的AudioAttributes.Builder是桩(Stub)实现,链式调用时setContentType()会返回null,后续调用setUsage()自然就抛出空指针异常——因为纯JUnit测试跑在本地JVM,没有加载完整的Android框架实现。
解决方案
方案1:用Robolectric模拟Android环境
Robolectric会提供Android框架类的真实模拟实现,支持链式调用,无需修改原代码:
- 项目中添加Robolectric依赖(根据你用的JUnit版本调整)
- 测试类上添加Runner注解:
@RunWith(RobolectricTestRunner::class) // JUnit4 // 或者JUnit5用@ExtendWith(RobolectricExtension::class) class PlayerTest { @Test fun testPlayerInitialization() { val mp = Player() // 正常执行测试逻辑,比如验证音频属性是否设置正确 val attributes = mp.audioAttributes assertEquals(AudioAttributes.CONTENT_TYPE_MUSIC, attributes.contentType) } @Test fun test_my_function() { // 测试逻辑 } }
方案2:修改Player类支持依赖注入
通过构造参数或工厂方法抽离音频属性的创建逻辑,让测试可以替换依赖:
方式A:直接传入AudioAttributes
修改Player类,提供带参数的构造方法:
class Player(private val audioAttributes: AudioAttributes) : MediaPlayer() { // 保留无参构造给生产环境用 constructor() : this( AudioAttributes.Builder() .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) .setUsage(AudioAttributes.USAGE_MEDIA) .build() ) fun myFunction() { /* ... */ } }
测试时传入Mock的AudioAttributes:
import org.mockito.Mockito.* class PlayerTest { @Test fun testPlayerInitialization() { val mockAttr = mock(AudioAttributes::class.java) val mp = Player(mockAttr) // 验证setAudioAttributes是否被正确调用 verify(mp).setAudioAttributes(mockAttr) } }
方式B:传入Builder对象
如果需要验证Builder的链式调用逻辑,可以让Player接收Builder参数:
class Player(audioAttrBuilder: AudioAttributes.Builder = AudioAttributes.Builder()) : MediaPlayer() { init { val attr = audioAttrBuilder .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) .setUsage(AudioAttributes.USAGE_MEDIA) .build() setAudioAttributes(attr) } fun myFunction() { /* ... */ } }
测试时Mock Builder并设置返回值:
import org.mockito.Mockito.* class PlayerTest { @Test fun testPlayerInitialization() { val mockBuilder = mock(AudioAttributes.Builder::class.java) val mockAttr = mock(AudioAttributes::class.java) // 配置Mock的链式调用返回自身 `when`(mockBuilder.setContentType(anyInt())).thenReturn(mockBuilder) `when`(mockBuilder.setUsage(anyInt())).thenReturn(mockBuilder) `when`(mockBuilder.build()).thenReturn(mockAttr) val mp = Player(mockBuilder) // 验证Builder的方法是否按预期调用 verify(mockBuilder).setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) verify(mockBuilder).setUsage(AudioAttributes.USAGE_MEDIA) verify(mp).setAudioAttributes(mockAttr) } }
方案3:使用Instrumented测试
如果不需要本地快速测试,直接在设备/模拟器上跑Instrumented测试,此时Android框架类是真实可用的,无需修改代码,测试逻辑和你原本写的一致即可:
import androidx.test.ext.junit.runners.AndroidJUnit4 import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class PlayerInstrumentedTest { @Test fun testPlayerInitialization() { val mp = Player() // 执行测试逻辑 } }
内容的提问来源于stack exchange,提问作者Kalle
相关产品推荐
相关产品推荐

