Тесты эспрессо вызывают «Исключение ClassNotFoundException при отключении» (ВОСПРОИЗВОДИТСЯ в базовом приложении)

#android #unmarshalling #onsaveinstancestate #onrestoreinstancestate #instancestate

Вопрос:

Похоже, что это несогласованное исключение всегда возникает, когда действие воссоздается с сохраненным состоянием экземпляра во время тестов эспрессо. Я воспроизвел его с помощью очень простого приложения для Android. Вот шаги:

  1. Создайте приложение для Android с двумя действиями, каждое из которых имеет одну кнопку. Кнопка на первом действии открывает второе действие. Кнопка на втором действии закрывает текущее действие.
  2. Добавьте тест эспрессо, который просто открывает первое действие, нажимает кнопку (чтобы открыть второе действие), затем нажимает кнопку второго действия (чтобы завершить второе действие и вернуться к первому действию).
  3. В вашем эмуляторе обязательно включите опцию «Не сохранять действия» в вашем эмуляторе.

В моем реальном приложении это зависит от активности, класс которой будет «неизвестен», чтобы вызвать отключение. В этом конкретном примере, по-видимому, это панель инструментов. Я обнаружил это, удалив определенные записи («androidx.жизненный цикл.BundlableSavedStateRegistry.key» и «android:viewHierarchyState») из сохраненного экземпляра указывают, что он будет обходить это исключение во время тестов эспрессо, но, конечно, тогда все восстанавливается неправильно. И я еще раз повторю, что это проблема только при выполнении тестов эспрессо. При выполнении тех же самых точных шагов теста вручную все выполняется правильно, и исключений нет.

Изменение версий sdk, похоже, тоже не помогает.

Это оно.

Вот весь кровавый код:

 public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
//        // This will cause the exception to not be thrown during espresso tests
//        if(savedInstanceState != null) {
//            savedInstanceState.remove("androidx.lifecycle.BundlableSavedStateRegistry.key");
//            savedInstanceState.remove("android:viewHierarchyState");
//        }
        

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.open_button).setOnClickListener(v -> startActivity(new Intent(MainActivity.this, ChildActivity.class)));
    }
}
 
 public class ChildActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_child);

        findViewById(R.id.close_button).setOnClickListener(v -> finish());
    }
}
 
 @LargeTest
@RunWith(AndroidJUnit4.class)
public class MainActivityTest {

    @Rule
    public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<>(MainActivity.class);

    @Test
    public void test() {
        onView(withId(R.id.open_button)).perform(click());
        onView(withId(R.id.close_button)).perform(click());
        onView(withId(R.id.open_button)).check(matches(isDisplayed()));
    }
}
 
 androidx.test.espresso.PerformException: Error performing 'single click - At Coordinates: 115, 272 and precision: 16, 16' on view 'view.getId() is <2131231192/com.example.myapplication:id/close_button>'.
    at androidx.test.espresso.PerformException$Builder.build(PerformException.java:1)
    at androidx.test.espresso.base.PerformExceptionHandler.handleSafely(PerformExceptionHandler.java:8)
    at androidx.test.espresso.base.PerformExceptionHandler.handleSafely(PerformExceptionHandler.java:9)
    at androidx.test.espresso.base.DefaultFailureHandler$TypedFailureHandler.handle(DefaultFailureHandler.java:4)
    at androidx.test.espresso.base.DefaultFailureHandler.handle(DefaultFailureHandler.java:5)
    at androidx.test.espresso.ViewInteraction.waitForAndHandleInteractionResults(ViewInteraction.java:8)
    at androidx.test.espresso.ViewInteraction.desugaredPerform(ViewInteraction.java:11)
    at androidx.test.espresso.ViewInteraction.perform(ViewInteraction.java:8)
    at com.example.myapplication.MainActivityTest.mainActivityTest(MainActivityTest.java:31)
    ... 32 trimmed
Caused by: android.os.BadParcelableException: ClassNotFoundException when unmarshalling: androidx.appcompat.widget.Toolbar$SavedState
    at android.os.Parcel.readParcelableCreator(Parcel.java:2839)
    at android.os.Parcel.readParcelable(Parcel.java:2765)
    at android.os.Parcel.readValue(Parcel.java:2668)
    at android.os.Parcel.readSparseArrayInternal(Parcel.java:3118)
    at android.os.Parcel.readSparseArray(Parcel.java:2351)
    at android.os.Parcel.readValue(Parcel.java:2725)
    at android.os.Parcel.readArrayMapInternal(Parcel.java:3037)
    at android.os.BaseBundle.initializeFromParcelLocked(BaseBundle.java:288)
    at android.os.BaseBundle.unparcel(BaseBundle.java:232)
    at android.os.Bundle.getSparseParcelableArray(Bundle.java:1010)
    at com.android.internal.policy.PhoneWindow.restoreHierarchyState(PhoneWindow.java:2133)
    at android.app.Activity.onRestoreInstanceState(Activity.java:1135)
    at android.app.Activity.performRestoreInstanceState(Activity.java:1090)
    at android.app.Instrumentation.callActivityOnRestoreInstanceState(Instrumentation.java:1317)
    at android.app.ActivityThread.handleStartActivity(ActivityThread.java:2953)
    at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:180)
    at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:165)
    at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:142)
    at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:70)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
    at android.os.Handler.dispatchMessage(Handler.java:106)
    at androidx.test.espresso.base.Interrogator.loopAndInterrogate(Interrogator.java:14)
    at androidx.test.espresso.base.UiControllerImpl.loopUntil(UiControllerImpl.java:8)
    at androidx.test.espresso.base.UiControllerImpl.loopUntil(UiControllerImpl.java:1)
    at androidx.test.espresso.base.UiControllerImpl.injectMotionEvent(UiControllerImpl.java:6)
    at androidx.test.espresso.action.MotionEvents.sendUp(MotionEvents.java:7)
    at androidx.test.espresso.action.MotionEvents.sendUp(MotionEvents.java:1)
    at androidx.test.espresso.action.Tap.sendSingleTap(Tap.java:5)
    at androidx.test.espresso.action.Tap.sendSingleTap$bridge(Unknown Source:0)
    at androidx.test.espresso.action.Tap$1.sendTap(Tap.java:3)
    at androidx.test.espresso.action.GeneralClickAction.perform(GeneralClickAction.java:6)
    at androidx.test.espresso.ViewInteraction$SingleExecutionViewAction.perform(ViewInteraction.java:2)
    at androidx.test.espresso.ViewInteraction.doPerform(ViewInteraction.java:25)
    at androidx.test.espresso.ViewInteraction.doPerform$bridge(Unknown Source:0)
    at androidx.test.espresso.ViewInteraction$1.call(ViewInteraction.java:2)
    at androidx.test.espresso.ViewInteraction$1.call(ViewInteraction.java:1)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at android.os.Handler.handleCallback(Handler.java:873)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:193)
    at android.app.ActivityThread.main(ActivityThread.java:6669)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
 

Ответ №1:

Из того, что я могу сказать, вы должны вместо этого использовать ActivityScenario, а затем проверять состояния различных действий с помощью ActivityScenario.moveToState или ActivityScenario.recreate.