Тестирование действия навигации в компоненте навигации в Android

Недавно я интегрировал компонент навигации в проект, основанный на модели одной активности. Я попытался добавить тесты пользовательского интерфейса на основе руководств в документах Android. Однако это не сработало, когда я немного изменил этот урок. У меня есть следующий код в моем фрагменте.

val bundle = Bundle().apply {
   putString(UserTrackingConstants.VIEW, UserTrackingConstants.HOME)
}

btnSignUp.setOnClickListener {
findNavController().navigate(R.id.action_from_home_fragment_to_registration_fragment,bundle)
}

Таким образом, всякий раз, когда нажимается btnSignUp, вызывается следующее действие навигации. Он работает идеально. Затем я добавил следующий тест.

@Test
    fun testRegisterButtonClicked() {
        val mockNavController = mock(NavController::class.java)
        val bundle = bundleOf(
                UserTrackingConstants.VIEW to UserTrackingConstants.HOME
        )
        val scenario = launchFragmentInContainer<HomeFragment>()
        scenario.onFragment { fragment ->
            Navigation.setViewNavController(fragment.requireView(), mockNavController)
        }
        onView(withId(R.id.btnSignUp))
                .perform(click())
        verify(mockNavController).navigate(R.id.action_from_home_fragment_to_registration_fragment, bundle)
    }

И он выдает следующее исключение

E/TestRunner: ----- begin exception -----
    Argument(s) are different! Wanted:
    navController.navigate(
        2131296310,
        Bundle[{view=home}]
    );
    -> at HomeFragmentTest.testRegisterButtonClicked(HomeFragmentTest.kt:87)
    Actual invocation has different arguments:
    navController.navigate(
        2131296310,
        Bundle[{view=home}]
    );
    -> at HomeFragment$setupObservers$2.onClick(HomeFragment.kt:84)

        at HomeFragmentTest.testRegisterButtonClicked(HomeFragmentTest.kt:87)
        at java.lang.reflect.Method.invoke(Native Method)
        at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
        at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
        at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
        at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
        at androidx.test.internal.runner.junit4.statement.RunBefores.evaluate(RunBefores.java:80)
        at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
        at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
        at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
        at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
        at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
        at androidx.test.ext.junit.runners.AndroidJUnit4.run(AndroidJUnit4.java:104)
        at org.junit.runners.Suite.runChild(Suite.java:128)
        at org.junit.runners.Suite.runChild(Suite.java:27)
        at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
        at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
        at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
        at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
        at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
        at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
        at androidx.test.internal.runner.TestExecutor.execute(TestExecutor.java:56)
        at androidx.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:392)
        at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2189)
    ----- end exception -----

Argument(s) are different! Wanted:
navController.navigate(
2131296310,
Bundle[{view=home}]
);
-> at HomeFragmentTest.testRegisterButtonClicked(HomeFragmentTest.kt:87)
Actual invocation has different arguments:
navController.navigate(
2131296310,
Bundle[{view=home}]
);
-> at HomeFragment$setupObservers$2.onClick(HomeFragment.kt:84)

at HomeFragmentTest.testRegisterButtonClicked(HomeFragmentTest.kt:87)
at java.lang.reflect.Method.invoke(Native Method)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at androidx.test.internal.runner.junit4.statement.RunBefores.evaluate(RunBefores.java:80)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at androidx.test.ext.junit.runners.AndroidJUnit4.run(AndroidJUnit4.java:104)
at org.junit.runners.Suite.runChild(Suite.java:128)
at org.junit.runners.Suite.runChild(Suite.java:27)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
at androidx.test.internal.runner.TestExecutor.execute(TestExecutor.java:56)
at androidx.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:392)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2189)

Я не получаю ничего полезного от logcat. Вроде аргументы одинаковые и должно пройти. Он работает нормально, если я не передаю пакет. Я следовал документам по следующей ссылке https://developer.android.com/guide/navigation/navigation-testing. Мой вопрос: кто-нибудь испытывает такие исключения и как протестировать компонент навигации с пакетом?


person Farruh Habibullaev    schedule 19.12.2019    source источник


Ответы (1)


Причина, по которой вы получили такие странные результаты тестов, заключается в том, что класс Bundle не переопределяет методы equals() и toString(), поэтому экземпляры Bundle сравниваются по ссылке (и ссылки, очевидно, отличаются для вашего ожидаемого пакета и фактического) — и поскольку Mockito полагается на этих методах, чтобы убедиться, что объекты одинаковы, ваш тестовый пример завершится ошибкой.

Чтобы исправить это поведение, вы можете использовать собственный сопоставитель для Mockito.verify():

public class BundleEquals implements ArgumentMatcher, ContainsExtraTypeInfo, Serializable {

    @Nullable
    private final Bundle expected;

    public BundleEquals(@Nullable Bundle expected) {
        this.expected = expected;
    }

    @Override
    public boolean matches(Bundle actual) {
        if (expected == null && actual == null) {
            return true;
        }

        if (expected == null || actual == null) {
            return false;
        }

        return areBundlesEqual(expected, actual);
    }

    private boolean areBundlesEqual(@NonNull Bundle expected, @NonNull Bundle actual) {
        if (expected.size() != actual.size()) {
            return false;
        }

        if (!expected.keySet().containsAll(actual.keySet())) {
            return false;
        }

        for (String key : expected.keySet()) {
            @Nullable Object expectedValue = expected.get(key);
            @Nullable Object actualValue = actual.get(key);

            if (expectedValue instanceof Bundle && actualValue instanceof Bundle) {
                if (!areBundlesEqual((Bundle) expectedValue, (Bundle) actualValue)) {
                    return false;
                }
            } else if (!Objects.equals(expectedValue, actualValue)) {
                return false;
            }
        }

        return true;
    }

    public String toStringWithType() {
        String clazz = expected != null ? expected.getClass().getSimpleName() : null;
        return "(" + clazz + ") " + describe(expected);
    }

    private String describe(Object object) {
        return ValuePrinter.print(object);
    }

    @Override
    public boolean typeMatches(Object actual) {
        return expected != null && actual != null && actual.getClass() == expected.getClass();
    }

    public String toString() {
        return describe(expected);
    }

}

Примечание. Реализации ContainsExtraTypeInfo и Serializable здесь не требуются, но они предоставят вам подробное описание ожидаемых и фактических аргументов, когда тест не пройден из-за несоответствия пакетов,

Чтобы применить сопоставитель, вы должны использовать что-то вроде этого:

verify(mockNavController).navigate(eq(R.id.action_from_home_fragment_to_registration_fragment), argThat(new BundleEquals(bundle));

Обратите внимание, что здесь необходимо указать eq(), чтобы Mockito не путал разные сопоставители: оставление идентификатора действия без упаковки приведет к сбою проверки.

person Nik Satyr    schedule 14.11.2020