Zhuinden / simple-stack

[ACTIVE] Simple Stack, a backstack library / navigation framework for simpler navigation and state management (for fragments, views, or whatevers).
Apache License 2.0
1.36k stars 76 forks source link

Backstack with espresso #281

Closed MikaReesu closed 10 months ago

MikaReesu commented 10 months ago

Hello, I am new to using espresso with Kotlin.

I'm using simple stack since a lot of time and since have a sometime I started to implement espresso.

Maybe it is my misunderstanding since it is new to me to use espresso.

`@RunWith(AndroidJUnit4::class) class FragmentTest {

@Rule
@JvmField
val activityRule = ActivityScenarioRule(MainActivity::class.java)

@Test
fun launchFragment() {
    launchFragmentInContainer<SignInFragment>(
        themeResId = R.style.MainTheme,
    )

}

    `

I created an activityRule to have the MainActivity running from start and after I wanted to start one of my fragment.

I get this error directly :

java.lang.NullPointerException: Attempt to invoke virtual method 'com.zhuinden.simplestack.Backstack com.zhuinden.simplestack.navigator.BackstackHost.getBackstack()' on a null object reference

I would like to know how I could set up the Backstack properly in the test itself or the @Before.

The Backstack is setup this way in my MainActivity : Navigator.configure() .setStateChanger(SimpleStateChanger(this)) .setGlobalServices(app.globalServices) .setScopedServices(DefaultServiceProvider()) .setShouldPersistContainerChild(false) .install(this, findViewById(R.id.container), History.of(SplashScreen()))

Espresso is new to me, so tell me if what I was doing is totally wrong and if you could give me a guidance on this it will be very helpful.

Thank you very much!

Zhuinden commented 10 months ago

@mikareesu this is an interesting question, because based on the FragmentScenario source code, the problem is that when you use launchFragmentInContainer<T: Fragment>, it internally uses an EmptyFragmentActivity rather than your MainActivity.

https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:fragment/fragment-testing-manifest/src/main/java/androidx/fragment/app/testing/EmptyFragmentActivity.kt;l=30

The EmptyFragmentActivity has support for a custom theme, but no other means of customization.

Based on FragmentScenario source code, this cannot be customized when using FragmentScenario.

https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:fragment/fragment-testing/src/main/java/androidx/fragment/app/testing/FragmentScenario.kt;l=515?q=launchFragmentInContainer

So even though you have Navigator.install() and a BackstackHost installed in MainActivity, it appears that the Activity in EmptyFragmentActivity has no idea how to do that. There seems to also be no way to access this class from the outside and make any necessary customizations. Makes me wonder what FragmentScenario is even doing, then.

Personally, when I was using Espresso, I was using getInstrumentation().runOnMainSync() directly instead of androidx.test.

However, it appears that ActivityScenario.onActivity does exactly that: https://github.com/android/android-test/blob/5d69602b7f5950e8c62027e7eeb7f613e5f3b015/core/java/androidx/test/core/app/ActivityScenario.java#L802

Which means that instead of using launchFragmentInContainer, you can theoretically use activityScenario.onActivity { activity -> Navigator.getBackstack(activity).setHistory(History.of(YourFragmentKey()), StateChange.REPLACE) } in order to get the fragment showing, and you don't need launchFragmentInContainer as that starts using EmptyFragmentActivity instead of your MainActivity.

Hope that helps.

MikaReesu commented 10 months ago

Thank you very much for this answer. It was indeed spot on.

To give the exact code to achieve that, because I just tried it and it's working wonderfully.

You need the Activity Rule : @Rule @JvmField val activityRule = ActivityScenarioRule(MainActivity::class.java) And the test to access to the Fragment :

`@Test fun testFragmentIsVisible() {

    activityRule.run {
        scenario.onActivity {
                activity -> Navigator.getBackstack(activity).setHistory(History.of(YourFragmentKey()), StateChange.REPLACE)
        }
    }

    // your action to modify the view or whatever you wanna do.
}`

You said, "when I was using espresso", does it mean that you use something else now?

Thank you for your help!

Zhuinden commented 10 months ago

You said, "when I was using espresso", does it mean that you use something else now?

Mistake in wording tbh. Espresso is still the best way to automate UI tests when using Views.

Thank you for your help!

Glad it works 😄