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"); }
为什么当前测试写法不对?
说白了,你的测试现在根本没验证任何实际逻辑:
- 你Mock了整个MainActivity:Mockito的mock对象是个“假”对象,调用它的
showDialogBox方法时,不会执行你写的真实弹窗逻辑——相当于你只是调用了一个空方法,根本没触发Dialog的创建。 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
相关产品推荐
相关产品推荐

