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

Android中Mockito测试Activity弹出AlertDialog方法的正确性及正确编写方式

如何正确用Mockito测试Android中弹出AlertDialog的方法?

嘿,我明白你现在的困惑——刚接触Mockito Android测试,写了测试用例虽然跑过了,但心里没底对吧?先来看下你的代码和当前测试的问题,再一步步给你讲正确的写法。

首先,先贴出你Activity里的弹窗方法:

fun showDialogBox( shortMessage: String?, longMessage: String?, progress: Int, status: String? ) {
 runOnUiThread {
 if (cdfwExtractionStatusAlertDialog == null) {
 val mDialogView = LayoutInflater.from(this).inflate(
 R.layout.cdfw_extraction_status_layout, null
 )
 val mBuilder = AlertDialog.Builder(this)
 .setView(mDialogView)
 .setCancelable(false)
 shortMessageTv = mDialogView.findViewById(R.id.short_message_tv)
 longMessageTv = mDialogView.findViewById(R.id.long_message_tv)
 closeButton = mDialogView.findViewById(R.id.close_button)
 cdfwProcessBar = mDialogView.findViewById(R.id.progress_bar)
 cdfwProcessBar.progress = progress
 shortMessageTv.text = shortMessage
 longMessageTv.text = longMessage
 closeButton.setOnClickListener {
 cdfwExtractionStatusAlertDialog?.dismiss()
 }
 checkStatus(status)
 cdfwExtractionStatusAlertDialog = mBuilder.show()
 } else {
 shortMessageTv.text = longMessage // 这里看起来是笔误?应该赋值shortMessage吧?
 longMessageTv.text = longMessage
 cdfwProcessBar.progress = progress
 checkStatus(status)
 }
 }
}

然后是你当前的测试代码:

@Test
public void testCDFWExtractionStatusNoZipFound(){
 MainActivity activity = Mockito.mock(MainActivity.class);
 activity.showDialogBox("","CDFW Required.",0,"FAILED_NO_ZIP_FOUND");
 Mockito.matches("CDFW Extraction Status");
 Mockito.matches("CDFW Required.");
 Mockito.matches("Close");
}

@Test
public void testCDFWExtractionStatusInProgress(){
 MainActivity activity = Mockito.mock(MainActivity.class);
 activity.showDialogBox("","CDFW extraction ongoing",0,"IN_PROGRESS");
 Mockito.matches("CDFW Extraction Status");
 Mockito.matches("CDFW extraction ongoing");
}

为什么当前测试写法不对?

说白了,你的测试现在根本没验证任何实际逻辑:

  1. 你Mock了整个MainActivity:Mockito的mock对象是个“假”对象,调用它的showDialogBox方法时,不会执行你写的真实弹窗逻辑——相当于你只是调用了一个空方法,根本没触发Dialog的创建。
  2. Mockito.matches()单独调用毫无意义:这个方法是用来创建匹配器的,比如在验证方法参数时用verify(mock).someMethod(matches("xxx")),但你单独写它,既没绑定到任何验证操作,也不会检查任何内容,所以测试肯定会“成功”,但完全没起到测试作用。

正确的测试写法(配合Robolectric)

要测试Android的AlertDialog这类UI组件,光靠Mockito不够,得用Robolectric——它能模拟Android运行环境,让你创建真实的Activity实例,还能处理UI线程的任务。

第一步:添加依赖(以Gradle为例)

在你的Module级别build.gradle里添加Robolectric和Mockito的依赖:

testImplementation "org.robolectric:robolectric:4.12.1"
testImplementation "org.mockito:mockito-core:5.6.0"
testImplementation "org.mockito:mockito-inline:5.6.0" // 支持spy final类

第二步:编写正确的测试用例

下面用Kotlin写测试(你用Java的话可以对应转换),核心思路是创建真实的Activity实例,触发弹窗方法,然后验证Dialog的各种属性和内容:

import android.widget.Button
import android.widget.ProgressBar
import android.widget.TextView
import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.*
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Shadows.shadowOf
import org.robolectric.shadows.ShadowAlertDialog

@RunWith(AndroidJUnit4::class)
class MainActivityTest {

    @Test
    fun testCDFWExtractionStatusNoZipFound() {
        // 1. 创建真实的MainActivity实例,模拟生命周期
        val activity = ActivityScenario.launch(MainActivity::class.java).get()

        // 2. 调用弹窗方法
        activity.showDialogBox("", "CDFW Required.", 0, "FAILED_NO_ZIP_FOUND")

        // 3. 触发UI线程任务执行(因为showDialogBox用了runOnUiThread)
        shadowOf(activity.application).mainLooper.idle()

        // 4. 获取当前显示的AlertDialog
        val dialog = ShadowAlertDialog.getLatestAlertDialog()
        assertNotNull("Dialog should be created", dialog)

        // 5. 验证Dialog的基础属性
        assertFalse("Dialog should not be cancelable", dialog.isCancelable)

        // 6. 获取Dialog的布局控件,验证内容
        val dialogView = dialog.findViewById<android.view.View>(R.id.cdfw_extraction_status_layout)
        val shortMessageTv = dialogView.findViewById<TextView>(R.id.short_message_tv)
        val longMessageTv = dialogView.findViewById<TextView>(R.id.long_message_tv)
        val closeButton = dialogView.findViewById<Button>(R.id.close_button)
        val progressBar = dialogView.findViewById<ProgressBar>(R.id.progress_bar)

        assertEquals("Short message should be empty", "", shortMessageTv.text.toString())
        assertEquals("Long message mismatch", "CDFW Required.", longMessageTv.text.toString())
        assertEquals("Close button text mismatch", "Close", closeButton.text.toString())
        assertEquals("Progress bar value mismatch", 0, progressBar.progress)

        // 7. 验证内部方法checkStatus的调用(如果需要的话)
        // 这里需要用spy包装Activity,因为要验证方法调用
        val activitySpy = org.mockito.Mockito.spy(activity)
        activitySpy.showDialogBox("", "CDFW Required.", 0, "FAILED_NO_ZIP_FOUND")
        shadowOf(activity.application).mainLooper.idle()
        org.mockito.Mockito.verify(activitySpy).checkStatus("FAILED_NO_ZIP_FOUND")
    }

    @Test
    fun testCDFWExtractionStatusInProgress() {
        val activity = ActivityScenario.launch(MainActivity::class.java).get()

        activity.showDialogBox("", "CDFW extraction ongoing", 0, "IN_PROGRESS")
        shadowOf(activity.application).mainLooper.idle()

        val dialog = ShadowAlertDialog.getLatestAlertDialog()
        assertNotNull(dialog)

        val dialogView = dialog.findViewById<android.view.View>(R.id.cdfw_extraction_status_layout)
        val longMessageTv = dialogView.findViewById<TextView>(R.id.long_message_tv)
        val progressBar = dialogView.findViewById<ProgressBar>(R.id.progress_bar)

        assertEquals("CDFW extraction ongoing", longMessageTv.text.toString())
        assertEquals(0, progressBar.progress)

        // 验证Dialog存在时的更新逻辑(比如再次调用showDialogBox)
        activity.showDialogBox("Updated short", "Updated long", 50, "IN_PROGRESS")
        shadowOf(activity.application).mainLooper.idle()

        // 注意:这里对应你原代码的else分支笔误,如果修正为shortMessageTv.text = shortMessage,这里要改成"Updated short"
        val updatedShortTv = dialogView.findViewById<TextView>(R.id.short_message_tv)
        assertEquals("Updated long", updatedShortTv.text.toString())
        assertEquals("Updated long", longMessageTv.text.toString())
        assertEquals(50, progressBar.progress)
    }
}

关键要点总结

  • 不要Mock被测Activity:要测试真实的弹窗逻辑,得用Robolectric创建真实的Activity实例,或者用spy(当需要验证内部方法调用时)。
  • 处理UI线程:runOnUiThread里的任务不会自动执行,需要用Robolectric的shadowOf(mainLooper).idle()来触发。
  • 验证实际行为:要检查Dialog是否创建、控件内容是否正确、进度条数值、按钮行为等,而不是写无用的匹配器调用。
  • 注意代码笔误:你原代码的else分支里shortMessageTv.text = longMessage看起来是笔误,应该赋值为shortMessage,测试时也要对应调整。

内容的提问来源于stack exchange,提问作者Harsh Shah

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 20:02:55