Checking toast message in android espresso
有谁知道如何测试android espresso中Toast消息的外观? 在robotium中,它很简单,我用过但开始在浓缩咖啡中使用,但未获得确切的命令。
这段简短的声明对我有用:
1 2 3 4 5 6 7 8 | import static android.support.test.espresso.assertion.ViewAssertions.matches; import static android.support.test.espresso.matcher.RootMatchers.withDecorView; import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; import static android.support.test.espresso.matcher.ViewMatchers.withText; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; .... onView(withText(R.string.TOAST_STRING)).inRoot(withDecorView(not(is(getActivity().getWindow().getDecorView())))).check(matches(isDisplayed())); |
公认的答案是一个很好的答案,但对我没有用。所以我搜索了一下,找到了这篇博客文章。
这给了我一个想法,我更新了上面的解决方案。
首先,我实现了Toa??stMatcher:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | import android.os.IBinder; import android.support.test.espresso.Root; import android.view.WindowManager; import org.hamcrest.Description; import org.hamcrest.TypeSafeMatcher; public class ToastMatcher extends TypeSafeMatcher<Root> { @Override public void describeTo(Description description) { description.appendText("is toast"); } @Override public boolean matchesSafely(Root root) { int type = root.getWindowLayoutParams().get().type; if (type == WindowManager.LayoutParams.TYPE_TOAST) { IBinder windowToken = root.getDecorView().getWindowToken(); IBinder appToken = root.getDecorView().getApplicationWindowToken(); if (windowToken == appToken) { // windowToken == appToken means this window isn't contained by any other windows. // if it was a window for an activity, it would have TYPE_BASE_APPLICATION. return true; } } return false; } } |
然后我实现了如下检查方法:
1 2 3 | public void isToastMessageDisplayed(int textId) { onView(withText(textId)).inRoot(MobileViewMatchers.isToast()).check(matches(isDisplayed())); } |
MobileViewMatchers是用于访问匹配器的容器。我在那里定义了静态方法
1 2 3 | public static Matcher<Root> isToast() { return new ToastMatcher(); } |
这对我来说就像一种魅力。
首先请确保导入:
1 2 3 4 | import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.matcher.ViewMatchers.withText; import static android.support.test.espresso.matcher.RootMatchers.withDecorView; import static android.support.test.espresso.assertion.ViewAssertions.matches; |
在您的班级内部,您可能会有如下规则:
1 2 3 | @Rule public ActivityTestRule<MyNameActivity> activityTestRule = new ActivityTestRule<>(MyNameActivity.class); |
测试内:
1 2 3 4 | MyNameActivity activity = activityTestRule.getActivity(); onView(withText(R.string.toast_text)). inRoot(withDecorView(not(is(activity.getWindow().getDecorView())))). check(matches(isDisplayed())); |
这对我有用,而且非常易于使用。
如果您使用的是Jetpack的最新Android测试工具,那么您会知道不推荐使用ActivityTestRule,而应该使用ActivityScenario或ActivityScenarioRule(其中包含第一个)。
先决条件。创建decorView变量并在测试之前分配它;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @Rule public ActivityScenarioRule<FeedActivity> activityScenarioRule = new ActivityScenarioRule<>(FeedActivity.class); private View decorView; @Before public void setUp() { activityScenarioRule.getScenario().onActivity(new ActivityScenario.ActivityAction<FeedActivity>() { @Override public void perform(FeedActivityactivity) { decorView = activity.getWindow().getDecorView(); } }); } |
自我测试
1 2 3 4 5 6 7 8 9 10 | @Test public void given_when_thenShouldShowToast() { String expectedWarning = getApplicationContext().getString(R.string.error_empty_list); onView(withId(R.id.button)) .perform(click()); onView(withText(expectedWarning)) .inRoot(withDecorView(not(decorView)))// Here we use decorView .check(matches(isDisplayed())); } |
getApplicationContext()可以从
尽管问题的答案是可以接受的-BTW不适用于我-我想在Kotlin中添加我的解决方案,该解决方案是我从Thomas R.的答案中得出的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | package somepkg import android.support.test.espresso.Espresso.onView import android.support.test.espresso.Root import android.support.test.espresso.matcher.ViewMatchers.withText import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY import android.view.WindowManager.LayoutParams.TYPE_TOAST import org.hamcrest.Description import org.hamcrest.Matcher import org.hamcrest.TypeSafeMatcher /** * This class allows to match Toast messages in tests with Espresso. * * Idea taken from: https://stackoverflow.com/a/33387980 * * Usage in test class: * * import somepkg.ToastMatcher.Companion.onToast * * // To assert a toast does *not* pop up: * onToast("text").check(doesNotExist()) * onToast(textId).check(doesNotExist()) * * // To assert a toast does pop up: * onToast("text").check(matches(isDisplayed())) * onToast(textId).check(matches(isDisplayed())) */ class ToastMatcher(private val maxFailures: Int = DEFAULT_MAX_FAILURES) : TypeSafeMatcher<Root>() { /** Restrict number of false results from matchesSafely to avoid endless loop */ private var failures = 0 override fun describeTo(description: Description) { description.appendText("is toast") } public override fun matchesSafely(root: Root): Boolean { val type = root.windowLayoutParams.get().type @Suppress("DEPRECATION") // TYPE_TOAST is deprecated in favor of TYPE_APPLICATION_OVERLAY if (type == TYPE_TOAST || type == TYPE_APPLICATION_OVERLAY) { val windowToken = root.decorView.windowToken val appToken = root.decorView.applicationWindowToken if (windowToken === appToken) { // windowToken == appToken means this window isn't contained by any other windows. // if it was a window for an activity, it would have TYPE_BASE_APPLICATION. return true } } // Method is called again if false is returned which is useful because a toast may take some time to pop up. But for // obvious reasons an infinite wait isn't of help. So false is only returned as often as maxFailures specifies. return (++failures >= maxFailures) } companion object { /** Default for maximum number of retries to wait for the toast to pop up */ private const val DEFAULT_MAX_FAILURES = 5 fun onToast(text: String, maxRetries: Int = DEFAULT_MAX_FAILURES) = onView(withText(text)).inRoot(isToast(maxRetries))!! fun onToast(textId: Int, maxRetries: Int = DEFAULT_MAX_FAILURES) = onView(withText(textId)).inRoot(isToast(maxRetries))!! fun isToast(maxRetries: Int = DEFAULT_MAX_FAILURES): Matcher<Root> { return ToastMatcher(maxRetries) } } } |
我希望这会对以后的读者有所帮助-用法在注释中进行了描述。
首先创建一个通用的Toast Matcher,我们可以在测试用例中使用它-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public class ToastMatcher extends TypeSafeMatcher<Root> { @Override public void describeTo(Description description) { description.appendText("is toast"); } @Override public boolean matchesSafely(Root root) { int type = root.getWindowLayoutParams().get().type; if ((type == WindowManager.LayoutParams.TYPE_TOAST)) { IBinder windowToken = root.getDecorView().getWindowToken(); IBinder appToken = root.getDecorView().getApplicationWindowToken(); if (windowToken == appToken) { //means this window isn't contained by any other windows. return true; } } return false; } } |
1.测试是否显示Toast消息
1 2 | onView(withText(R.string.mssage)).inRoot(new ToastMatcher()) .check(matches(isDisplayed())); |
2.测试是否未显示Toast消息
1 2 | onView(withText(R.string.mssage)).inRoot(new ToastMatcher()) .check(matches(not(isDisplayed()))); |
3.测试ID Toast包含特定的文本消息
1 2 | onView(withText(R.string.mssage)).inRoot(new ToastMatcher()) .check(matches(withText("Invalid Name")); |
谢谢,
阿奴加
注意-此答案来自此POST。
我写我的自定义吐司匹配器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | import android.view.WindowManager import androidx.test.espresso.Root import org.hamcrest.Description; import org.hamcrest.TypeSafeMatcher; class ToastMatcher : TypeSafeMatcher<Root>() { override fun describeTo(description: Description) { description.appendText("is toast") } override fun matchesSafely(root: Root): Boolean { val type = root.getWindowLayoutParams().get().type if (type == WindowManager.LayoutParams.TYPE_TOAST) { val windowToken = root.getDecorView().getWindowToken() val appToken = root.getDecorView().getApplicationWindowToken() if (windowToken === appToken) { return true } } return false } } |
像这样使用:
1 | onView(withText(R.string.please_input_all_fields)).inRoot(ToastMatcher()).check(matches(isDisplayed())) |
我想说吐司消息首先定义您的规则
1 2 3 | @Rule public ActivityTestRule<AuthActivity> activityTestRule = new ActivityTestRule<>(AuthActivity.class); |
然后,您要查找的任何吐司消息文本都在引号之间键入
例如我使用"无效的电子邮件地址"
1 2 3 | onView(withText("Invalid email address")) .inRoot(withDecorView(not(activityTestRule.getActivity().getWindow().getDecorView()))) .check(matches(isDisplayed())); |
对于kotlin,我必须使用Apply扩展功能,这对我来说很有用。
1-在androidTest文件夹中声明您的ToastMatcher类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | class ToastMatcher : TypeSafeMatcher<Root?>() { override fun matchesSafely(item: Root?): Boolean { val type: Int? = item?.windowLayoutParams?.get()?.type if (type == WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW) { val windowToken: IBinder = item.decorView.windowToken val appToken: IBinder = item.decorView.applicationWindowToken if (windowToken === appToken) { // means this window isn't contained by any other windows. return true } } return false } override fun describeTo(description: Description?) { description?.appendText("is toast") } } |
2-然后您可以像这样使用以测试吐司消息是否实际显示
1 2 3 4 | onView(withText(R.string.invalid_phone_number)) .inRoot(ToastMatcher().apply { matches(isDisplayed()) }); |
归因于ToastMatcher类:
1 2 3 | /** * Author: http://www.qaautomated.com/2016/01/how-to-test-toast-message-using-espresso.html */ |
我想建议一种替代方法,尤其是在您需要检查未显示特定吐司的情况下
这里的问题是
1 2 3 | onView(viewMatcher) .inRoot(RootMatchers.isPlatformPopup()) .check(matches(not(isDisplayed()))) |
要么
1 2 3 | onView(viewMatcher) .inRoot(RootMatchers.isPlatformPopup()) .check(doesNotExist()) |
或任何其他自定义
在代码传递给
您可能只捕获了异常并完成了测试,但这不是一个好选择,因为与默认测试用例相比,抛出和捕获
对于这种情况,建议您在这里放弃使用意式浓缩咖啡,并使用
1 2 3 4 5 6 7 8 9 10 11 12 | val device: UiDevice get() = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) fun assertPopupIsNotDisplayed() { device.waitForIdle() assertFalse(device.hasObject(By.text(yourText)))) } fun assertPopupIsDisplayed() { device.waitForIdle() assertTrue(device.hasObject(By.text(yourText)))) } |
this works for me
onView(withId(R.id.inputField))。check(matches(withText(" Lalala"))));
我对此很陌生,但是我创建了一个基类'BaseTest',该类具有我所有的操作(滑动,单击等)和验证(检查文本视图中的内容等)。
1 2 3 4 5 6 7 | protected fun verifyToastMessageWithText(text: String, activityTestRule: ActivityTestRule<*>) { onView(withText(text)).inRoot(withDecorView(not(activityTestRule.activity.window.decorView))).check(matches(isDisplayed())) } protected fun verifyToastMessageWithStringResource(id: Int, activityTestRule: ActivityTestRule<*>) { onView(withText(id)).inRoot(withDecorView(not(activityTestRule.activity.window.decorView))).check(matches(isDisplayed())) } |
实施Toast的方式可以检测到已显示的Toast。但是,无法通过调用show()来查看是否已请求Toast,也无法在show()与Toast可见之间的时间段之间进行阻塞。这带来了无法解决的计时问题(您只能通过睡眠和希望来解决)。
如果您真的想验证这一点,可以使用Mockito和一个测试间谍,这是一个不太漂亮的选择:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | public interface Toaster { public void showToast(Toast t); private static class RealToaster { @Override public void showToast(Toast t) { t.show(); } public static Toaster makeToaster() { return new RealToaster(); } } Then in your test public void testMyThing() { Toaster spyToaster = Mockito.spy(Toaster.makeToaster()); getActivity().setToaster(spyToaster); onView(withId(R.button)).perform(click()); getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { // must do this on the main thread because the matcher will be interrogating a view... Mockito.verify(spyToaster).showToast(allOf(withDuration(Toast.LENGTH_SHORT), withView(withText("hello world")); }); } // create a matcher that calls getDuration() on the toast object Matcher<Toast> withDuration(int) // create a matcher that calls getView() and applies the given view matcher Matcher<Toast> withView(Matcher<View> viewMatcher) another answer regarding this if(someToast == null) someToast = Toast.makeText(this,"sdfdsf", Toast.LENGTH_LONG); boolean isShown = someToast.getView().isShown(); |