You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为继承MediaPlayer的Kotlin类编写单元测试并解决空指针异常

问题原因

本地单元测试环境下,Android SDK提供的AudioAttributes.Builder是桩(Stub)实现,链式调用时setContentType()会返回null,后续调用setUsage()自然就抛出空指针异常——因为纯JUnit测试跑在本地JVM,没有加载完整的Android框架实现。

解决方案

方案1:用Robolectric模拟Android环境

Robolectric会提供Android框架类的真实模拟实现,支持链式调用,无需修改原代码:

  1. 项目中添加Robolectric依赖(根据你用的JUnit版本调整)
  2. 测试类上添加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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 02:57:06