在Android Espresso测试中断言异常

问题描述:

我在Espresso中进行了一项测试,该测试需要断言某个操作会引发异常.

I have a test in Espresso that needs to assert that a certain action cause an exception to be thrown.

然而,Espresso框架似乎吞下了原始异常,只出现了一个 PerformException .

However it seems that the Espresso framework swallows the original exception and only surfaces a PerformException.

最终,我找到了一种方法.我创建了一个自定义的Hamcrest匹配器,可让您验证嵌套异常.

Eventually I found a way to do it. I've created a custom Hamcrest matcher that allows you to verify a nested exception.

public class NestedExceptionMatcher extends TypeSafeMatcher<Throwable> {

    private final Class<?> mExpectedType;
    private final Matcher<String> mMessageMatcher;

    static NestedExceptionMatcher expectNestedThrowable(Class<?> expectedType, Matcher<String> messageMatcher) {
        return new NestedExceptionMatcher(expectedType, messageMatcher);
    }

    NestedExceptionMatcher(Class<?> expectedType, Matcher<String> messageMatcher) {
        mExpectedType = expectedType;
        mMessageMatcher = messageMatcher;
    }

    @Override
    protected boolean matchesSafely(Throwable item) {
        boolean matches = isMatch(item);

        Throwable currentThrowable = item.getCause();
        while (!matches && currentThrowable != null) {
            matches = isMatch(currentThrowable);
            currentThrowable = currentThrowable.getCause();
        }

        return matches;
    }

    @Override
    public void describeTo(Description description) {
        description
                .appendText("expects type ")
                .appendValue(mExpectedType)
                .appendText(" with a message ")
                .appendDescriptionOf(mMessageMatcher);
    }

    private boolean isMatch(Throwable t) {
        return t.getClass().isAssignableFrom(mExpectedType) && mMessageMatcher.matches(t.getMessage());
    }
}

然后您可以在测试中按如下方式使用它:

And then you can use it as follows in your test:

public class SomeActivityTest {

    @Rule
    public ActivityTestRule<SomeActivity> mRule = new ActivityTestRule<>(
            SomeActivity.class, false, false);

    @Rule
    public ExpectedException mExceptionRule = ExpectedException.none();

    @Test
    public void testClick_whenInvalidParamsAreSet_shouldThrowException() {
        mRule.launchActivity(getIntentWithInvalidParams());
        mExceptionRule.expect(expectNestedThrowable(
                IllegalArgumentException.class,
                containsString("parameter isn't defined")
        ));

        onView(withId(R.id.my_btn)).perform(click());
    }

    private Intent getIntentWithInvalidParams() {
        ...
    }
}