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 75 forks source link

How to write unit tests based on the readme example #286

Closed cjimenezCT closed 3 months ago

cjimenezCT commented 3 months ago

I'm trying to setup navigation unit testing for my Compose project but I'm getting stuck on actually getting the backstack to progress past enqueueing the state change. Is there something missing in my setup? Should this instead be done as an instrumented test?

Test

@Test
fun goToLogin_backstackHasCorrectHistory() {
    val backstack = Backstack()
    backstack.setup(History.of(DashboardKey()))
    val viewModel = DashboardViewModel(backstack)

    assert(!backstack.isStateChangePending) // passes

    viewModel.goToLogin()

    assert(backstack.isStateChangePending) // passes

    val expected = listOf(LoginKey())
    val actual = backstack.getHistory<DefaultComposeKey>()
    assert(actual == expected) // fails because actual is empty
}

View model

class DashboardViewModel(
    private val backstack: Backstack,
) : ScopedServices.Registered {
    // ...

    fun goToLogin() {
        // todo logout logic

        backstack.setHistory(
            History.of(LoginKey()),
            StateChange.BACKWARD,
        )
    }

    // ...
}

LoginKey

@Immutable
@Parcelize
data object LoginKey : ComposeKey() {
    operator fun invoke() = this

    override fun bindServices(serviceBinder: ServiceBinder) {
        super.bindServices(serviceBinder)
        with(serviceBinder) {
            add(LoginViewModel(backstack))
        }
    }

    @Composable
    override fun ScreenComposable(modifier: Modifier) {
        val viewModel = rememberService<LoginViewModel>()

        LoginScreen(viewModel)
    }
}
Zhuinden commented 3 months ago

Hello,

It should be as simple as also setting a state changer.

        Backstack backstack = new Backstack();
        backstack.setup(History.of(first));
        backstack.setStateChanger(new StateChanger() {
            @Override
            public void handleStateChange(@Nonnull StateChange stateChange, @Nonnull Callback completionCallback) {
                completionCallback.stateChangeComplete();
            }
        });

or

backstack.setStateChanger { _, completionCallback -> completionCallback.stateChangeComplete() }
cjimenezCT commented 3 months ago

Thank you, this worked but with the addition of setScopedServices:

val backstack = Backstack()
backstack.setup(History.of(DashboardKey()))
backstack.setScopedServices(DefaultServiceProvider())
backstack.setStateChanger { _, completionCallback -> completionCallback.stateChangeComplete() }
Zhuinden commented 3 months ago

Ah yes, that's correct, as scoped services is technically a separate, configurable add-on.

But yes, that'll work correctly. 👍