如何在Android 11中使用Espresso验证Toast是否显示?
在Android 11中使用Espresso验证Toast显示的解决方案
Android 11及更高版本对Toast的显示机制做了调整——Toast不再由应用进程托管,而是由系统进程负责渲染,这直接导致原本针对Android 10及以下版本的withDecorView匹配方法失效,因为Espresso无法跨进程捕获系统层级的Toast窗口。
下面提供几种可行的解决方案:
方案一:使用UiAutomator跨进程检测Toast
UiAutomator支持跨进程获取系统UI元素,能直接检测到系统托管的Toast,是兼容性最好的方案。
步骤1:添加UiAutomator依赖
在模块的build.gradle中添加依赖:
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
步骤2:编写测试代码
import androidx.test.uiautomator.UiDevice; import androidx.test.uiautomator.UiSelector; import androidx.test.uiautomator.Until; import android.widget.Toast; import androidx.test.platform.app.InstrumentationRegistry; // 在测试方法中 UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); // 匹配指定文本的Toast UiSelector toastSelector = new UiSelector() .text("Toast message here") .className(Toast.class.getName()); // 等待Toast显示,超时时间5秒 assertTrue("Toast未正常显示", device.wait(Until.hasObject(toastSelector), 5000));
方案二:自定义Toast管理类(需修改应用代码)
如果允许修改应用源码,可以通过单例类统一管理Toast的显示状态,测试时直接读取该状态即可,无需依赖系统窗口检测。
步骤1:实现Toast管理类
public class ToastManager { private static String currentToastText; public static void showToast(Context context, String text) { currentToastText = text; Toast.makeText(context, text, Toast.LENGTH_SHORT).show(); } public static String getCurrentToastText() { return currentToastText; } // 测试完成后清理状态,避免影响其他测试用例 public static void clearToastState() { currentToastText = null; } }
步骤2:替换应用中的Toast调用
将原本的Toast.makeText()替换为ToastManager.showToast()。
步骤3:编写测试代码
// 触发显示Toast的操作 onView(withId(R.id.your_button_id)).perform(click()); // 验证Toast内容 assertEquals("Toast message here", ToastManager.getCurrentToastText()); // 测试结束后清理状态 ToastManager.clearToastState();
这种方案的优势是测试速度快,不受系统版本限制;缺点是需要侵入应用代码,适合内部可控的项目。
方案三:自定义RootMatcher(兼容性有限)
部分设备上可以通过扩展Espresso的RootMatcher来匹配系统Toast窗口,但稳定性不如前两种方案,仅作参考:
import android.view.WindowManager; import android.view.View; import androidx.test.espresso.Root; import org.hamcrest.Description; import org.hamcrest.TypeSafeMatcher; public class ToastRootMatcher extends TypeSafeMatcher<Root> { @Override protected boolean matchesSafely(Root root) { int windowType = root.getWindowLayoutParams().get().type; // 匹配Toast或应用覆盖层类型的窗口 if (windowType == WindowManager.LayoutParams.TYPE_TOAST || windowType == WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY) { View decorView = root.getDecorView(); return decorView.getWindowToken() != decorView.getApplicationWindowToken(); } return false; } @Override public void describeTo(Description description) { description.appendText("匹配Toast窗口"); } }
测试时使用该Matcher:
Espresso.onView(withText("Toast message here")) .inRoot(new ToastRootMatcher()) .check(matches(isDisplayed()));
总结
- 优先选择方案一(UiAutomator),无需修改应用代码,兼容性覆盖Android 11及以上版本;
- 若项目允许修改应用代码,方案二是更高效的测试方式;
- 方案三仅适合部分场景,稳定性不足,谨慎使用。
内容的提问来源于stack exchange,提问作者Nnt
相关产品推荐
相关产品推荐

