【问题标题】:Android Espresso: How to check that Toast message is NOT shown?Android Espresso:如何检查未显示 Toast 消息?
【发布时间】:2015-04-27 12:46:29
【问题描述】:

我现在正在我的功能测试中工作,其中一个我必须测试没有显示 toast 消息。考虑到这是我用来检查是否显示 toast 消息的代码(此代码有效):

onView(withText(R.string.my_toast_message))
        .inRoot(withDecorView(not(getActivity().getWindow().getDecorView())))
        .check(matches(isDisplayed()));

您可以在下面找到我用来检查 Toast 消息是否未显示的代码(它们都不起作用):

方法一:

onView(withText(R.string.error_invalid_login))
        .inRoot(withDecorView(not(getActivity().getWindow().getDecorView())))
        .check(matches(not(isDisplayed())));

方法二:

onView(withText(R.string.error_invalid_login))
        .inRoot(withDecorView(not(getActivity().getWindow().getDecorView())))
        .check(doesNotExist());

任何关于如何检查 toast 消息是否未显示的想法将不胜感激:)

【问题讨论】:

  • 您的第二种方法对我来说似乎是正确的。如果你说它不起作用,你会得到什么样的意外行为?
  • 第二种方法对我有用。正如@apppoll 所说,在测试中运行第二种方法的结果是什么?
  • 我的方法二出现了 NoMatchingRootException,所以它对我也不起作用,你让它起作用了吗?

标签: android testing functional-testing android-espresso


【解决方案1】:

需要在 toast 不存在时捕获这种情况,为此会抛出 NoMatchingRootException。下面显示了捕捉它的“Espresso 方式”。

public static Matcher<Root> isToast() {
    return new WindowManagerLayoutParamTypeMatcher("is toast", WindowManager.LayoutParams.TYPE_TOAST);
}
public static void assertNoToastIsDisplayed() {
    onView(isRoot())
            .inRoot(isToast())
            .withFailureHandler(new PassMissingRoot())
            .check(matches(not(anything("toast root existed"))))
    ;
}

使用上述方法的快速(自我)测试:

@Test public void testToastMessage() {
    Toast toast = createToast("Hello Toast!");
    assertNoToastIsDisplayed();
    toast.show();
    onView(withId(android.R.id.message))
            .inRoot(isToast())
            .check(matches(withText(containsStringIgnoringCase("hello"))));
    toast.cancel();
    assertNoToastIsDisplayed();
}

private Toast createToast(final String message) {
    final AtomicReference<Toast> toast = new AtomicReference<>();
    InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
        @SuppressLint("ShowToast") // will be shown later
        @Override public void run() {
            toast.set(Toast.makeText(InstrumentationRegistry.getContext(), message, Toast.LENGTH_LONG));
        }
    });
    return toast.get();
}

神奇的可重用辅助类:

public class PassMissingRoot implements FailureHandler {
    private final FailureHandler defaultHandler
            = new DefaultFailureHandler(InstrumentationRegistry.getTargetContext());
    @Override public void handle(Throwable error, Matcher<View> viewMatcher) {
        if (!(error instanceof NoMatchingRootException)) {
            defaultHandler.handle(error, viewMatcher);
        }
    }
}

public class WindowManagerLayoutParamTypeMatcher extends TypeSafeMatcher<Root> {
    private final String description;
    private final int type;
    private final boolean expectedWindowTokenMatch;
    public WindowManagerLayoutParamTypeMatcher(String description, int type) {
        this(description, type, true);
    }
    public WindowManagerLayoutParamTypeMatcher(String description, int type, boolean expectedWindowTokenMatch) {
        this.description = description;
        this.type = type;
        this.expectedWindowTokenMatch = expectedWindowTokenMatch;
    }
    @Override public void describeTo(Description description) {
        description.appendText(this.description);
    }
    @Override public boolean matchesSafely(Root root) {
        if (type == root.getWindowLayoutParams().get().type) {
            IBinder windowToken = root.getDecorView().getWindowToken();
            IBinder appToken = root.getDecorView().getApplicationWindowToken();
            if (windowToken == appToken == expectedWindowTokenMatch) {
                // 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;
    }
}

【讨论】:

  • 您可以通过在matchesSafely 的开头添加if (timeout[0]) throw new RuntimeException("Searched for root until timeout"); 来轻松添加超时。然后几秒钟后从外面设置timeout[0]=true。如果您改用自定义异常,则可以在测试用例中捕获它。
【解决方案2】:

在 espresso 中测试 toast 消息的最佳方法是使用自定义匹配器:

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 false;
    }
}

您可以在测试用例中使用它:

  1. 测试是否显示 Toast 消息

    onView(withText(R.string.message)).inRoot(new ToastMatcher())
    .check(matches(isDisplayed()));
    
  2. 测试 Toast 消息是否不显示

    onView(withText(R.string.message)).inRoot(new ToastMatcher())
    .check(matches(not(isDisplayed())));
    
  3. 测试 id 吐司包含特定文本消息

    onView(withText(R.string.message)).inRoot(new ToastMatcher())
    .check(matches(withText("Invalid Name"));
    

我从我的博客中复制了这个答案 - http://qaautomated.blogspot.in/2016/01/how-to-test-toast-message-using-espresso.html

【讨论】:

  • 如果您没有 Toast 的 ID,您会怎么做?即说你没有 R.string.message?
  • 您可以根据toast消息中显示的内容添加完整的文本。
  • 这不是一个好的选择,因为与默认测试用例相比,抛出和捕获NoMatchingRootException 会消耗大量时间。似乎 Espresso 正在等待 Root 一段时间
  • 在这个例子中没有返回true?
【解决方案3】:

这行得通

boolean exceptionCaptured = false;
try {
  onView(withText(R.string.error_invalid_login))
          .inRoot(withDecorView(not(is(getActivity().getWindow().getDecorView()))))
          .check(doesNotExist());
}catch (NoMatchingRootException e){
  exceptionCaptured = true;
}finally {
  assertTrue(exceptionCaptured);
}

【讨论】:

  • 作为旁注,此代码既可以存在也可以不存在。我们需要做的就是正确设置布尔标志。很好的答案。
  • 这不是一个好的选择,因为与默认测试用例相比,抛出和捕获 NoMatchingRootException 会消耗大量时间。似乎 Espresso 正在等待 Root 一段时间
【解决方案4】:

例如,如果您不仅有吐司还有PopupWindow,那么使用浓缩咖啡似乎不可能进行这种简单的检查。

对于这种情况,建议在这里放弃浓缩咖啡并使用UiAutomator 进行此断言

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))))
}

【讨论】:

    【解决方案5】:

    我知道已经晚了,但可能这会对其他人有所帮助。

        onView(withText("Test")).inRoot(withDecorView(not(mActivityRule.getActivity().getWindow().getDecorView())))
                .check(doesNotExist());
    

    【讨论】:

      【解决方案6】:

      就像@anuja jain 的回答,但如果您得到NoMatchingRootException,您可以注释掉if ((type == WindowManager.LayoutParams.TYPE_TOAST)) 检查并将return true; 行添加到内部if 块。

      【讨论】:

        【解决方案7】:

        尝试以下解决方案

        onView(withId(android.R.id.message))
                        .inRoot(withDecorView(not(is(mRule.getActivity().getWindow().getDecorView()))))
                        .check(matches(withText("Some message")));
        

        【讨论】:

          【解决方案8】:

          您可以查看源代码here 并创建您自己的视图匹配器,它的作用正好相反。

          【讨论】:

          • 1.仅链接(已损坏),2. 不是答案,问题不是相反的,3. 使用不存在的视图/根不是微不足道的
          猜你喜欢
          • 2015-04-08
          • 2011-12-09
          • 2015-02-12
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-07-04
          相关资源
          最近更新 更多