Проверка тост сообщения в Android эспрессо

Кто-нибудь знает, как проверить наличие сообщения Toast в эспрессо Android? В robotium это легко и я использовал, но начал работать в эспрессо, но не получил точную команду.

7 ответов

Решение

Это немного длинное утверждение работает для меня:

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()));

Принятый ответ хороший, но у меня не получилось. Поэтому я немного искал и нашел эту статью в блоге. Это дало мне представление о том, как это сделать, и я обновил решение выше.

Сначала я реализовал ToastMatcher:

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

}

Затем я реализовал свои методы проверки следующим образом:

public void isToastMessageDisplayed(int textId) {
    onView(withText(textId)).inRoot(MobileViewMatchers.isToast()).check(matches(isDisplayed()));
}

MobileViewMatchers - это контейнер для доступа к сопоставителям. Там я определил статический метод isToast(),

public static Matcher<Root> isToast() {
    return new ToastMatcher();
}

Это работает как шарм для меня.

Если вы используете новейшие инструменты тестирования Android из Jetpack, вы знаете, что ActivityTestRule устарела, и вам следует использовать ActivityScenario или ActivityScenarioRule(который содержит первое).

Предпосылки. Создать переменную decorView и назначить ее перед тестами;

    @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();
            }
        });
}

Проверь себя

@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() можно взять из androidx.test.core.app.ApplicationProvider.getApplicationContext;

Сначала убедитесь, что импортировали:

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;

Внутри вашего класса у вас, вероятно, есть такое правило:

@Rule
public ActivityTestRule<MyNameActivity> activityTestRule =
            new ActivityTestRule<>(MyNameActivity.class);

Внутри вашего теста:

MyNameActivity activity = activityTestRule.getActivity();
onView(withText(R.string.toast_text)).
    inRoot(withDecorView(not(is(activity.getWindow().getDecorView())))).
    check(matches(isDisplayed()));

Это сработало для меня, и было довольно легко использовать.

Хотя на вопрос есть принятый ответ - что, кстати, для меня не работает, - я бы хотел добавить свое решение в Котлине, которое я получил из ответа Томаса Р.:

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://stackru.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)
        }
    }

}

Я надеюсь, что это поможет последующим читателям - использование описано в комментарии.

Сначала создайте Cutom Toast Matcher, который мы можем использовать в наших тестах.

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.mssage)).inRoot(new ToastMatcher())
.check(matches(isDisplayed()));

2. Проверьте, не отображается ли сообщение о тосте.

onView(withText(R.string.mssage)).inRoot(new ToastMatcher())
.check(matches(not(isDisplayed())));

3. Идентификатор теста, который содержит тост

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

Спасибо ануя

Обратите внимание - этот ответ из этого поста.

Для kotlin мне пришлось использовать функцию расширения apply, и это сработало для меня.

1- объявите свой класс ToastMatcher в папке androidTest:

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- Затем вы используете это, чтобы проверить, действительно ли отображается тост-сообщение

onView(withText(R.string.invalid_phone_number))
        .inRoot(ToastMatcher().apply {
            matches(isDisplayed())
        });

Принадлежность к классу ToastMatcher:

/**
 * Author: http://www.qaautomated.com/2016/01/how-to-test-toast-message-using-espresso.html
 */

Я пишу свой тостер:

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

И используйте так:

onView(withText(R.string.please_input_all_fields)).inRoot(ToastMatcher()).check(matches(isDisplayed()))

Я хотел бы предложить альтернативный метод, особенно если вам нужно проверить, что конкретный тост НЕ отображается.

Проблема здесь в том, что

onView(viewMatcher)
    .inRoot(RootMatchers.isPlatformPopup())
    .check(matches(not(isDisplayed())))

или

onView(viewMatcher)
    .inRoot(RootMatchers.isPlatformPopup())
    .check(doesNotExist())

или любой другой обычай inRoot чеки бросают NoMatchingRootException еще до того, как код перейдет в check метод

Вы можете просто перехватить исключение и завершить тест, но это не лучший вариант, поскольку бросать и ловить NoMatchingRootExceptionзанимает много времени по сравнению с тестовым случаем по умолчанию. Кажется, что Эспрессо пока ждет корня

В этом случае предлагается просто отказаться от эспрессо и использовать UiAutomatorдля этого утверждения. ВEspresso а также 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))))
}

Я бы сказал, для тостов сначала определите ваше правило

 @Rule
   public ActivityTestRule<AuthActivity> activityTestRule =
   new ActivityTestRule<>(AuthActivity.class);

затем какой бы текст сообщения вы ни искали, введите его в кавычках, например, я использовал "Неверный адрес электронной почты"

   onView(withText("Invalid email address"))
    .inRoot(withDecorView(not(activityTestRule.getActivity().getWindow().getDecorView())))
    .check(matches(isDisplayed()));

Использование ActivityScenarioRule и Java

Некоторый импорт для кода

import android.view.View;
import androidx.test.ext.junit.rules.ActivityScenarioRule;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.matcher.RootMatchers.withDecorView;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.not;

1. Объявите правило

//Change YourActivity by the activity you are testing
@Rule
public ActivityScenarioRule<YourActivity> activityRule
        = new ActivityScenarioRule<>(YourActivity.class);

2. Инициализируйте вид декора.

    private View decorView;

    @Before
    public void loadDecorView() {
        activityRule.getScenario().onActivity(
                activity -> decorView = activity.getWindow().getDecorView()
        );
    }

3. Наконец проверьте это

    @Test
    public void testWithToasts() {


        //Arrange and act code

        //Modify toast_msg to your own string resource
        onView(withText(R.string.toast_msg)).
                inRoot(RootMatchers.withDecorView(not(decorView)))
                .check(matches(isDisplayed()));
    }

Я довольно новичок в этом, но я создал базовый класс "BaseTest", в котором есть все мои действия (перелистывание, нажатие и т. Д.) И проверки (проверка текстовых представлений на предмет содержимого и т. Д.).

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

это работает для меня

onView(withId(R.id.inputField)).check(соответствует (withText("Lalala")));

Способ реализации тостов позволяет обнаружить тост, который был отображен. Однако нет способа узнать, был ли запрошен тост, через вызов show()) или заблокировать промежуток времени между show () и моментом, когда тост стал видимым. Это открывает неразрешимые проблемы времени (которые вы можете решить только через сон и надежду).

Если вы действительно хотите это проверить, вот не очень приятная альтернатива с использованием Mockito и тестового шпиона:

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();
Другие вопросы по тегам