question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Is this the intended use with StateFlow?

See original GitHub issue

Recently I used turbine to test some StateFlow’s and came across a particular interaction and I’m mostly wondering what is the intended use. Both of the below cases fail.

oven.preWarm(300)
oven.states.test { state ->
  assertEquals(WARMING(100), expectItem())
  assertEquals(WARMING(200), expectItem())
  assertEquals(PREWARM_COMPLETE, expectItem())

  cancelAndIgnoreRemainingEvents()
}

For this case calling expectItem() only receives the final state expected (PREWARM_COMPLETE) from the preWarm() call and will fail the assertions. This makes sense as the collect operator is starting when our StateFlow has already emitted up to this state.

oven.states.test { state ->
  assertEquals(WARMING(100), expectItem())
  assertEquals(WARMING(200), expectItem())
  assertEquals(PREWARM_COMPLETE, expectItem())

  cancelAndIgnoreRemainingEvents()
}

oven.preWarm(300)

For this case there is an exception for a default timeout: kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1000 ms

or for timeout = Duration.ZERO java.lang.IllegalStateException: This job has not completed yet


However, when used like the below, moving the call to trigger state emission to the validate lambda, all expected events are received and the test passes. This is because validate is called after collection starts on the receiver flow.

oven.states.test { state ->
  oven.preWarm(300)
  assertEquals(WARMING(100), expectItem())
  assertEquals(WARMING(200), expectItem())
  assertEquals(PREWARM_COMPLETE, expectItem())

  cancelAndIgnoreRemainingEvents()
}

And also launching in a new job and calling test and the emission after succeeds as well.

launch {
  oven.states.test { state ->
    assertEquals(WARMING(100), expectItem())
    assertEquals(WARMING(200), expectItem())
    assertEquals(PREWARM_COMPLETE, expectItem())

    cancelAndIgnoreRemainingEvents()
  }

  oven.preWarm(300)
}

The documentation provided so far doesn’t provide much information for a best practice on how to handle Flows that have their emissions triggered elsewhere. What is the best practice here? I would assume that launching a new job should not be required.


Note: I am using this rule to runBlockingTests

@ExperimentalCoroutinesApi
class MainCoroutineRule(
    val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher(),
) : TestWatcher() {

    override fun starting(description: Description?) {
        super.starting(description)
        Dispatchers.setMain(testDispatcher)
    }

    override fun finished(description: Description?) {
        super.finished(description)
        Dispatchers.resetMain()
        testDispatcher.cleanupTestCoroutines()
    }

    /**
     * A convenience function to prevent the need to call a long chain for the provided dispatcher
     */
    fun runBlockingTest(block: suspend TestCoroutineScope.() -> Unit) {
        testDispatcher.runBlockingTest { block() }
    }
}

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:5
  • Comments:15 (1 by maintainers)

github_iconTop GitHub Comments

11reactions
JakeWhartoncommented, Nov 19, 2020

The version which calls preWarm inside of the test block is what I would recommend here and in general. The lifetime of the lambda which is passed to test is the lifetime of the collection of the upstream flow. Any side-effects that you need to trigger should be done inside the lambda so that they can be observed.

I can try to find a way to talk about this more clearly in the README or somewhere because I can see it not being entirely obvious how you write tests with side-effects that are needed to trigger events.

4reactions
yusufceylancommented, Mar 7, 2021

You are right @matthewcmckenna . As you suggested I moved mainViewModel.setEvent(MainContract.Event.OnFetchPosts) inside the .test block and now I can retrieve all emitted events.

I also wrote tests for usecases in my domain layer with Flow. It is super easy to test with Turbine 😃 now I want to test my ViewModels and uiStates with StateFlows. Thanks to you it works now

Read more comments on GitHub >

github_iconTop Results From Across the Web

Operations for Stateflow Data - MATLAB & Simulink - MathWorks
Stateflow ® charts in Simulink ® models have an action language property that defines the operations that you can use in state and...
Read more >
Action Language (Stateflow)
Develop and test your application using double- or single-precision floating-point numbers. Using double- or single-precision floating-point numbers does not ...
Read more >
Control Logic Made Easy with Stateflow - YouTube
See what's new in the latest release of MATLAB and Simulink : ... to model complex decision logic using state diagrams and flow...
Read more >
Going deep on Flows & Channels — Part 5: StateFlows
The Flow API is simpler to use than the Channel API, so we have a component that works just like a Channel, but...
Read more >
StateFlow APIs in Kotlin - MindOrks
Now, when we run the app we get the desired output. This is how we can use the StateFlow to manage state in...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found