Slide 1

Slide 1 text

Mohit Sarveiya Unit Testing Kotlin Flows @heyitsmohit

Slide 2

Slide 2 text

Unit Testing Kotlin Flows ● Foundations ● Useful Libraries ● State Flows ● Shared Flows

Slide 3

Slide 3 text

Foundations How run blocking test works

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

Flow of data

Slide 7

Slide 7 text

flowOfData: Flow .filter { it.locationType == LocationType.RESTAURANT } .map { it.name }

Slide 8

Slide 8 text

flowOfData: Flow .filter { ... } .map { ... } .flatmap { ... }

Slide 9

Slide 9 text

flowOfData: Flow .filter { ... } .map { ... } .flatmap { ... } How do we test this flow?

Slide 10

Slide 10 text

Test Cases ● Successfully received data

Slide 11

Slide 11 text

@Test fun `should get data`() { }

Slide 12

Slide 12 text

@Test fun `should get data`() = runBlockingTest { } Creates a coroutine

Slide 13

Slide 13 text

@Test fun `should get data`() = runBlockingTest { } Test Scope

Slide 14

Slide 14 text

@Test fun `should get data`() = runBlockingTest { } Test Scope + Test Dispatcher

Slide 15

Slide 15 text

@Test fun `should get data`() = runBlockingTest { } Test Scope + Test Dispatcher What is executed in this coroutine?

Slide 16

Slide 16 text

@Test fun `should get data`() = runBlockingTest { } Test Scope + Test Dispatcher

Slide 17

Slide 17 text

@Test fun `should get data`() = runBlockingTest { } Test Scope + Test Dispatcher

Slide 18

Slide 18 text

flowOfData: Flow .filter { ... } .map { ... } .flatmap { ... }

Slide 19

Slide 19 text

@Test fun `should get data`() = runBlockingTest { val flowOfData = mockFlow() val locations: List = flowOfLocations.toList() } Create Mock Flow

Slide 20

Slide 20 text

@Test fun `should get data`() = runBlockingTest { val flowOfData = mockFlow() val items: List = flowOfData.toList() }

Slide 21

Slide 21 text

@Test fun `should get locations`() = runBlockingTest { val items: List = flowOfData.toList() }

Slide 22

Slide 22 text

@Test fun `should get locations`() = runBlockingTest { val items: List = flowOfData.toList() }

Slide 23

Slide 23 text

@Test fun `should get locations`() = runBlockingTest { val items: List = flowOfData.toList() }

Slide 24

Slide 24 text

@Test fun `should get locations`() = runBlockingTest { val items: List = flowOfData.toList() }

Slide 25

Slide 25 text

@Test fun `should get locations`() = runBlockingTest { val items: List = flowOfData.toList() items shouldContainAll listOf(item1, item2, item3) }

Slide 26

Slide 26 text

@Test fun `should get data`() = runBlockingTest { val flowOfData = mockFlow() val items: List = flowOfData.toList() items shouldContainAll listOf(item1, item2, item3) }

Slide 27

Slide 27 text

@Test fun `should get data`() = runBlockingTest { val locations: List = flowOfLocations.toList() locations shouldContainAll listOf(location1, location2, location3) } How does this work?

Slide 28

Slide 28 text

fun runBlockingTest(testBody) { val scope = TestCoroutineScope(safeContext) val deferred = scope.async { scope.testBody() } deferred.getCompletionExceptionOrNull() ?. let { throw it } scope.cleanupTestCoroutines() if (hasActiveJobs()) { throw UncompletedCoroutinesError("Test finished with active jobs: $endingJobs") } } Our test to run

Slide 29

Slide 29 text

fun runBlockingTest(testBody) { val scope = TestCoroutineScope(safeContext) 
 
 val deferred = scope.async { scope.testBody() } dispatcher.advanceUntilIdle() deferred.getCompletionExceptionOrNull() ?. let { throw it } 
 
 scope.cleanupTestCoroutines() if (activeJobs()).isNotEmpty()) { throw UncompletedCoroutinesError("Test finished with active jobs") } } Setup

Slide 30

Slide 30 text

fun runBlockingTest(testBody) { val scope = TestCoroutineScope(safeContext) 
 
 val deferred = scope.async { scope.testBody() } dispatcher.advanceUntilIdle() deferred.getCompletionExceptionOrNull() ?. let { throw it } 
 
 scope.cleanupTestCoroutines() if (activeJobs()).isNotEmpty()) { throw UncompletedCoroutinesError("Test finished with active jobs") } } Setup Run Test

Slide 31

Slide 31 text

fun runBlockingTest(testBody) { val scope = TestCoroutineScope(safeContext) 
 
 val deferred = scope.async { scope.testBody() } dispatcher.advanceUntilIdle() deferred.getCompletionExceptionOrNull() ?. let { throw it } 
 
 scope.cleanupTestCoroutines() if (activeJobs()).isNotEmpty()) { throw UncompletedCoroutinesError("Test finished with active jobs") } } Setup Run Test Cleanup

Slide 32

Slide 32 text

@Test fun `should get data`() = runBlockingTest { val flowOfData = mockFlow() val items: List = flowOfData.toList() items shouldContainAll listOf(items1, items2, items3) }

Slide 33

Slide 33 text

Run Blocking Test Different Approaches 1. Use run blocking test for all tests 2. Testing Delays -> Use run blocking test 3. Non Delay Uses Cases -> Use run blocking

Slide 34

Slide 34 text

Useful Libraries Turbine

Slide 35

Slide 35 text

@Test fun `should get data`() = runBlockingTest { val flowOfLocations = mockFlow() val items: List = flowOfData.toList() locations shouldContainAll listOf(location1, location2, location3) } Better way to collect from Flows?

Slide 36

Slide 36 text

cashApp/turbine Code ! Issues Pull Requests Turbine Small testing library for kotlinx.coroutines Flow. testImplementation ‘app.cash.turbine:turbine:x.x.x'

Slide 37

Slide 37 text

Test Cases ● Verify order of items ● Completion ● Error ● Timeouts

Slide 38

Slide 38 text

What Turbine Provides? suspend fun Flow.test( ... ) coroutineScope { val events = Channel> (UNLIMITED) 
 launch { try { collect { ... } } catch { events.send(Event.Error) }

Slide 39

Slide 39 text

interface FlowTurbine { suspend fun expectItem(): T fun expectNoEvents() fun expectError(): Throwable suspend fun expectComplete() } What Turbine Provides?

Slide 40

Slide 40 text

interface FlowTurbine { suspend fun expectItem(): T fun expectNoEvents() fun expectError(): Throwable suspend fun expectComplete() } What Turbine Provides?

Slide 41

Slide 41 text

interface FlowTurbine { suspend fun expectItem(): T fun expectNoEvents() fun expectError(): Throwable suspend fun expectComplete() } What Turbine Provides?

Slide 42

Slide 42 text

interface FlowTurbine { suspend fun expectItem(): T fun expectNoEvents() fun expectError(): Throwable suspend fun expectComplete() } What Turbine Provides?

Slide 43

Slide 43 text

Test Cases ● Verify order of items ● Completion ● Error ● Timeouts

Slide 44

Slide 44 text

@Test fun `should get data`() = runBlockingTest { flowOfData.test { expectItem() shouldBeEqualTo location1 expectItem() shouldBeEqualTo location2 expectItem() shouldBeEqualTo location3 expectComplete() } 
 }

Slide 45

Slide 45 text

@Test fun `should get data`() = runBlockingTest { flowOfData.test { expectItem() shouldBeEqualTo location1 expectItem() shouldBeEqualTo location2 expectItem() shouldBeEqualTo location3 expectComplete() } 
 } Get first emitted item

Slide 46

Slide 46 text

@Test fun `should get data`() = runBlockingTest { flowOfData.test { expectItem() shouldBeEqualTo item1 expectItem() shouldBeEqualTo location2 expectItem() shouldBeEqualTo location3 expectComplete() } 
 } Assertion

Slide 47

Slide 47 text

@Test fun `should get data`() = runBlockingTest { flowOfData.test { expectItem() shouldBeEqualTo item1 expectItem() shouldBeEqualTo item2 expectItem() shouldBeEqualTo item3 expectComplete() } 
 } Get 1st, 2nd, 3rd item

Slide 48

Slide 48 text

@Test fun `should get data`() = runBlockingTest { flowOfData.test { expectItem() shouldBeEqualTo item1 expectItem() shouldBeEqualTo item2 } 
 } Missed collecting item

Slide 49

Slide 49 text

@Test fun `should get data`() = runBlockingTest { flowOfData.test { expectItem() shouldBeEqualTo item1 expectItem() shouldBeEqualTo item2 } 
 } RepoTest.kt Run: app.cash.turbine.AssertionError: Expected complete but found Item(Data(…)) DataRepoTests should get data

Slide 50

Slide 50 text

Test Cases ● Verify order of items ● Completion ● Error ● Timeouts

Slide 51

Slide 51 text

@Test fun `should get locations`() = runBlockingTest { flowOfData.test { expectItem() shouldBeEqualTo item1 expectItem() shouldBeEqualTo item2 expectItem() shouldBeEqualTo item3 expectComplete() } 
 }

Slide 52

Slide 52 text

@Test fun `should get locations`() = runBlockingTest { flowOfData.test { expectItem() shouldBeEqualTo item1 expectItem() shouldBeEqualTo item2 expectItem() shouldBeEqualTo item3 } 
 } RepoTests.kt Run: DataRepoTests should get data app.cash.turbine.AssertionError: Unconsumed events found: 
 - Complete

Slide 53

Slide 53 text

@Test fun `should get locations`() = runBlockingTest { flowOfData.test { expectItem() shouldBeEqualTo item1 expectItem() shouldBeEqualTo item2 expectItem() shouldBeEqualTo item3 expectComplete() } 
 }

Slide 54

Slide 54 text

How Turbine Works

Slide 55

Slide 55 text

Flow to test How Turbine Works

Slide 56

Slide 56 text

Channel (Unlimited) How Turbine Works

Slide 57

Slide 57 text

Channel Send Receive

Slide 58

Slide 58 text

How Turbine Works Channel (Unlimited) Event

Slide 59

Slide 59 text

sealed class Event { 
 object Complete 
 data class Error( ... ) 
 data class Item(val value: T) 
 } How Turbine Works Channel (Unlimited) Event

Slide 60

Slide 60 text

data class Item(val value: T): Event() How Turbine Works Channel (Unlimited)

Slide 61

Slide 61 text

data class Item(val value: T): Event() How Turbine Works Channel (Unlimited)

Slide 62

Slide 62 text

data class Item(val value: T): Event() How Turbine Works Channel (Unlimited)

Slide 63

Slide 63 text

object Complete: Event() How Turbine Works Channel (Unlimited)

Slide 64

Slide 64 text

Exception data class Error( ... ): Event() How Turbine Works Channel (Unlimited)

Slide 65

Slide 65 text

object Complete: Event() How Turbine Works Channel (Unlimited) How do I query items in channels?

Slide 66

Slide 66 text

interface FlowTurbine { val timeout: Duration fun expectNoEvents() suspend fun expectItem(): T fun expectError(): Throwable suspend fun expectComplete() } API to query Channel How Turbine Works

Slide 67

Slide 67 text

interface FlowTurbine { val timeout: Duration fun expectNoEvents() suspend fun expectItem(): T fun expectError(): Throwable suspend fun expectComplete() } How Turbine Works

Slide 68

Slide 68 text

How Turbine Works suspend fun Flow.test( ... ) { coroutineScope { } Create a coroutine

Slide 69

Slide 69 text

How Turbine Works suspend fun Flow.test( ... ) { coroutineScope { val events = Channel> (UNLIMITED) } Channel of events

Slide 70

Slide 70 text

How Turbine Works suspend fun Flow.test( ... ) { coroutineScope { val events = Channel> (UNLIMITED) 
 launch { } }

Slide 71

Slide 71 text

How Turbine Works suspend fun Flow.test( ... ) { coroutineScope { val events = Channel> (UNLIMITED) 
 launch { collect { item ->
 events.send(Event.Item(item)) } } Store emissions

Slide 72

Slide 72 text

How Turbine Works suspend fun Flow.test( validate: suspend FlowTurbine.() -> Unit ) { coroutineScope { val events = Channel> (UNLIMITED) 
 launch { try { collect { ... } } catch { events.send(Event.Error) API

Slide 73

Slide 73 text

Channel (Unlimited) Testing With Turbine Data

Slide 74

Slide 74 text

Channel (Unlimited) Testing With Turbine Data

Slide 75

Slide 75 text

Channel (Unlimited) Testing With Turbine Query Channel in test

Slide 76

Slide 76 text

Channel (Unlimited) Testing With Turbine Verify Flow completed

Slide 77

Slide 77 text

@Test fun `should get locations`() = runBlockingTest { flowOfLocations.test { expectItem() shouldBeEqualTo location1 expectItem() shouldBeEqualTo location2 expectItem() shouldBeEqualTo location3 expectComplete() } 
 }

Slide 78

Slide 78 text

Testing State Flows State Flow Use Cases

Slide 79

Slide 79 text

State Flow Consumer 1 Consumer 2 State State

Slide 80

Slide 80 text

State Flow View View Model

Slide 81

Slide 81 text

State Flow View View Model State

Slide 82

Slide 82 text

State Flow sealed class UiState { 
 data class Error( 
 val exception: Throwable 
 ): UiState() }

Slide 83

Slide 83 text

State Flow sealed class UiState { 
 data class Success( val data: Data ): UiState() data class Error( 
 val exception: Throwable 
 ): UiState() }

Slide 84

Slide 84 text

State Flow sealed class UiState { 
 data class Success( val data: Data ): UiState() data class Error( 
 val exception: Throwable 
 ): UiState() }

Slide 85

Slide 85 text

State Flow val stateFlow = MutableStateFlow()

Slide 86

Slide 86 text

val stateFlow = MutableStateFlow( UiState.Success(Data()) ) State Flow

Slide 87

Slide 87 text

State Flow val stateFlow = MutableStateFlow(UiState.Success(Data())) @Test fun `should emit default value`() = runBlockingTest { stateFlow.test { expectItem() shouldBe UIState.Success } }

Slide 88

Slide 88 text

State Flow Behavior ● Flow never completes normally

Slide 89

Slide 89 text

State Flow @Test fun `should emit default value`() = runBlockingTest { stateFlow.test { expectItem() shouldBe UIState.Success 
 expectComplete() } }

Slide 90

Slide 90 text

State Flow @Test fun `should emit default value`() = runBlockingTest { stateFlow.test { expectItem() shouldBe UIState.Success 
 expectComplete() } } ! Timed out waiting for 1000 ms kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1000 ms

Slide 91

Slide 91 text

State Flow Test Coroutine State Flow

Slide 92

Slide 92 text

State Flow suspend fun Flow.test( ... ) { coroutineScope { val events = Channel> (UNLIMITED) 
 launch { collect { item ->
 events.send(Event.Item(item)) } events.send(Event.Complete) } }

Slide 93

Slide 93 text

State Flow Behavior ● Flow never completes normally ● Subscribers can complete exceptionally

Slide 94

Slide 94 text

val stateFlow = MutableStateFlow(UiState.Success(Data())) State Flow stateFlow .onCompletion { println("ON COMPLETE") } .collect { println(it) }

Slide 95

Slide 95 text

val stateFlow = MutableStateFlow(UiState.Success(Data())) State Flow stateFlow .onCompletion { println("ON COMPLETE") } .collect { println(it) } Will not log

Slide 96

Slide 96 text

@Test fun `should emit default value`() = runBlockingTest { stateFlow .onCompletion { println("ON COMPLETE") } .test { expectItem() shouldBe UIState.Success } } State Flow

Slide 97

Slide 97 text

@Test fun `should emit default value`() = runBlockingTest { stateFlow .onCompletion { println("ON COMPLETE") } .test { expectItem() shouldBe UIState.Success } } State Flow Will log

Slide 98

Slide 98 text

How Turbine Works suspend fun Flow.test( ... ) { coroutineScope { 
 val collectJob = launch { collect { item -> ... } ... } ... collectJob.cancel() }

Slide 99

Slide 99 text

@Test fun `should emit default value`() = runBlockingTest { stateFlow .onCompletion { println("ON COMPLETE") } .test { expectItem() shouldBe UIState.Success } } State Flow Will log

Slide 100

Slide 100 text

State Flow Features ● Conflate Flow Emissions ● Get most recent emitted item

Slide 101

Slide 101 text

val stateFlow = MutableStateFlow(UIState.Success) @Test fun `should emit default value`() = runBlockingTest { stateFlow.emit(UIState.Error) stateFlow.test { expectItem() shouldBe UIState.Success expectItem() shouldBe UIState.Error } } State Flow

Slide 102

Slide 102 text

val stateFlow = MutableStateFlow(UIState.Success) @Test fun `should emit default value`() = runBlockingTest { stateFlow.emit(UIState.Error) stateFlow.test { expectItem() shouldBe UIState.Success expectItem() shouldBe UIState.Error } } State Flow

Slide 103

Slide 103 text

val stateFlow = MutableStateFlow(UIState.Success) @Test fun `should emit default value`() = runBlockingTest { stateFlow.emit(UIState.Error) stateFlow.test { expectItem() shouldBe UIState.Success expectItem() shouldBe UIState.Error } } State Flow Fail

Slide 104

Slide 104 text

val stateFlow = MutableStateFlow(UIState.Success) @Test fun `should emit default value`() = runBlockingTest { stateFlow.emit(UIState.Error) stateFlow.test { expectItem() shouldBe UIState.Success expectItem() shouldBe UIState.Error } } State Flow Most Recent Emission

Slide 105

Slide 105 text

val stateFlow = MutableStateFlow(UIState.Success) @Test fun `should emit default value`() = runBlockingTest { stateFlow.emit(UIState.Error) stateFlow.test { expectItem() shouldBe UIState.Error } } State Flow Success

Slide 106

Slide 106 text

State Flow Features ● Conflate Flow Emissions ● Get most recent emitted item

Slide 107

Slide 107 text

Mocking Flows View View Model Repository

Slide 108

Slide 108 text

Mocking Flows View Model Repository

Slide 109

Slide 109 text

Mocking Flows View Model Repository How to create a fake?

Slide 110

Slide 110 text

Mocking Flows Approaches ● Channels ● State/Shared Flows

Slide 111

Slide 111 text

Mocking Flows interface Repository { fun getData(): Flow }

Slide 112

Slide 112 text

Mocking Flows class FakeRepository: Repository { private val channel = Channel() 
 }

Slide 113

Slide 113 text

Mocking Flows class FakeRepository: Repository { private val channel = Channel() 
 override fun getData(): Flow { return channel.consumeAsFlow() } }

Slide 114

Slide 114 text

Mocking Flows class FakeRepository: Repository { private val channel = Channel() 
 suspend fun emitResult(result: Result) { channel.send(result) } }

Slide 115

Slide 115 text

Mocking Flows class FakeRepository: Repository { private val channel = Channel() 
 suspend fun emitFailure(result: Result.Failure) { channel.send(result) } }

Slide 116

Slide 116 text

Mocking Flows class FakeRepository: Repository { private val channel = Channel() 
 fun closeChannel() = apply { channel.close() } }

Slide 117

Slide 117 text

Mocking Flows Approaches ● Channels ● State/Shared Flows

Slide 118

Slide 118 text

Mocking Flows class FakeRepository: Repository { val _stateFlow = MutableStateFlow(Result.Success) 
 }

Slide 119

Slide 119 text

Mocking Flows class FakeRepository: Repository { val _stateFlow = MutableStateFlow(Result.Success) 
 val stateFlow = _stateFlow.asStateFlow() }

Slide 120

Slide 120 text

Mocking Flows class FakeRepository: Repository { val _stateFlow = MutableStateFlow(Result.Success) 
 fun emitResult(result: Result) { _stateFlow.value = result } }

Slide 121

Slide 121 text

State Flow Considerations ● Hard to test back to back emissions ● Conflates emissions

Slide 122

Slide 122 text

Better Approach ● Use Shared Flow ● Test the effects of each emission

Slide 123

Slide 123 text

State Flow View View Model State

Slide 124

Slide 124 text

State Flow State Flow Flow 1 Flow 2 Flow 3 Combined Flow

Slide 125

Slide 125 text

Flow Combine 1 A 1A Flow 1 Flow 2 Combined

Slide 126

Slide 126 text

Flow Combine 1 2 A 1A 2A Flow 1 Flow 2 Combined

Slide 127

Slide 127 text

Flow Combine 1 2 A 1A 2A Flow 1 Flow 2 Combined B 2B

Slide 128

Slide 128 text

State Flow View View Model State Holder

Slide 129

Slide 129 text

val stateFlow = MutableStateFlow(UIState.Success) 
 flow1.combine(flow2) { a, b -> } State Flow

Slide 130

Slide 130 text

val stateFlow = MutableStateFlow(UIState.Success) 
 flow1.combine(flow2) { a, b -> combineItems(a, b) } State Flow

Slide 131

Slide 131 text

val stateFlow = MutableStateFlow(UIState.Success) 
 flow1.combine(flow2) { a, b -> combineItems(a, b) }.collect { stateFlow.emit(it) } State Flow

Slide 132

Slide 132 text

@Test fun `should combine flows`() = runBlockingTest { 
 val mockFlow1 = mockFlow() 
 val mockFlow2 = mockFlow() } State Flow

Slide 133

Slide 133 text

@Test fun `should combine flows`() = runBlockingTest { 
 val mockFlow1 = mockFlow() 
 val mockFlow2 = mockFlow() 
 val stateHolder = StateHolder(mockFlow1, mockFlow2) } State Flow

Slide 134

Slide 134 text

@Test fun `should combine flows`() = runBlockingTest { 
 
 val stateHolder = StateHolder(mockFlow1, mockFlow2) 
 stateFlow.test { 
 ... 
 } } State Flow

Slide 135

Slide 135 text

State Flow Challenges ● Why didn’t the flow emit an item? Conflated? ● Too many flow combines makes it harder to follow the 
 
 data flow

Slide 136

Slide 136 text

Testing Shared Flows Shared Flow Use Cases

Slide 137

Slide 137 text

Shared Flow

Slide 138

Slide 138 text

Shared Flow Consumer 1 Consumer 2

Slide 139

Slide 139 text

Shared Flow Consumer 1 Consumer 2

Slide 140

Slide 140 text

Shared Flow Consumer 1 Consumer 2 Event Event

Slide 141

Slide 141 text

Shared Flow Consumer 1 Consumer 2 Replay Replay

Slide 142

Slide 142 text


 Shared Flow Buffer

Slide 143

Slide 143 text

Shared Flow val flow = MutableSharedFlow <> ()

Slide 144

Slide 144 text

Shared Flow val flow = MutableSharedFlow()

Slide 145

Slide 145 text

Shared Flow val flow = MutableSharedFlow() @Test fun `collect from shared flow`() = runBlockingTest { flow.emit("Event 1") }

Slide 146

Slide 146 text

Shared Flow val flow = MutableSharedFlow() @Test fun `collect from shared flow`() = runBlockingTest { flow.emit("Event 1") flow.test { expectItem() shouldBeEqualTo "Event 1" } } Failed

Slide 147

Slide 147 text

Shared Flow val flow = MutableSharedFlow() @Test fun `collect from shared flow`() = runBlockingTest { flow.emit("Event 1") flow.test { expectItem() shouldBeEqualTo "Event 1" } } Subscriber count 0

Slide 148

Slide 148 text

Shared Flow val flow = MutableSharedFlow() @Test fun `collect from shared flow`() = runBlockingTest { flow.emit("Event 1") flow.test { expectItem() shouldBeEqualTo "Event 1" } } Flow doesn’t replay

Slide 149

Slide 149 text

Shared Flow @Test fun `collect from shared flow`() = runBlockingTest { 
 val job = launch(start = CoroutineStart.LAZY) { flow.emit("Event 1") } }

Slide 150

Slide 150 text

Shared Flow @Test fun `collect from shared flow`() = runBlockingTest { 
 val job = launch(start = CoroutineStart.LAZY) { flow.emit("Event 1") } }

Slide 151

Slide 151 text

Shared Flow @Test fun `collect from shared flow`() = runBlockingTest { 
 val job = launch(start = CoroutineStart.LAZY) { flow.emit("Event 1") } flow.test { job.start() expectItem() shouldBeEqualTo "Event 1" } } Subscriber count 1

Slide 152

Slide 152 text

Shared Flow @Test fun `collect from shared flow`() = runBlockingTest { 
 val job = launch(start = CoroutineStart.LAZY) { flow.emit("Event 1") } flow.test { job.start() expectItem() shouldBeEqualTo "Event 1" } } Success

Slide 153

Slide 153 text

Shared Flow Features ● Replay

Slide 154

Slide 154 text

Shared Flow val flow = MutableSharedFlow(replay = 1)

Slide 155

Slide 155 text

@Test fun `collect from shared flow`() = runBlockingTest { flow.emit("Event 1") flow.test { expectItem() shouldBeEqualTo "Event 1" } } Shared Flow val flow = MutableSharedFlow(replay = 1) Replay

Slide 156

Slide 156 text

Mocking Flows View Model Repository How to create a fake?

Slide 157

Slide 157 text

Mocking Flows Approaches ● Channels ● State/Shared Flows

Slide 158

Slide 158 text

Mocking Flows class FakeRepository: Repository { val flow = MutableSharedFlow( 
 replay = 1, 
 BufferOverFlow.DROP_OLDEST 
 ) 
 }

Slide 159

Slide 159 text

Mocking Flows class FakeRepository: Repository { val flow = MutableSharedFlow( 
 replay = 1, 
 BufferOverFlow.DROP_OLDEST 
 ) 
 suspend fun emitResult(result: Result) { flow.emit(result) } }

Slide 160

Slide 160 text

Shared Flow Approach ● No conflation ● Handle testing each emission

Slide 161

Slide 161 text

Converting to Shared Flows ● While Subscribed ● Eagerly ● Lazily

Slide 162

Slide 162 text

While Subscribed val flow = upstreamFlow()

Slide 163

Slide 163 text

flow.shareIn( sharingScope, SharingStarted.WhileSubscribed(), replay = 1 ) While Subscribed

Slide 164

Slide 164 text

flow.shareIn( sharingScope, SharingStarted.WhileSubscribed(), replay = 1 ) While Subscribed

Slide 165

Slide 165 text

flow.shareIn( sharingScope, SharingStarted.WhileSubscribed(), replay = 1 ) While Subscribed

Slide 166

Slide 166 text

flow.shareIn( sharingScope, SharingStarted.WhileSubscribed(), replay = 1 ) While Subscribed

Slide 167

Slide 167 text

While Subscribed @Test fun `collect with while subscribed strategy`() = runBlockingTest { 
 val sharingScope = TestCoroutineScope() val sharedFlow = flow .onStart { println("ON START") } .shareIn( sharingScope, SharingStarted.WhileSubscribed(), 1 ) }

Slide 168

Slide 168 text

While Subscribed @Test fun `collect with while subscribed strategy`() = runBlockingTest { 
 val sharingScope = TestCoroutineScope() val sharedFlow = flow .shareIn( sharingScope, SharingStarted.WhileSubscribed(), 1 ) }

Slide 169

Slide 169 text

While Subscribed @Test fun `collect with while subscribed strategy`() = runBlockingTest { 
 val sharingScope = TestCoroutineScope() val sharedFlow = flow .onStart { println("ON START") } .shareIn( sharingScope, SharingStarted.WhileSubscribed(), 1 ) } Will not log

Slide 170

Slide 170 text

While Subscribed ● Start upstream flow upon subscriber ● Remain active while external scope is active

Slide 171

Slide 171 text

While Subscribed @Test fun `collect with while subscribed strategy`() = runBlockingTest { 
 val sharedFlow = flow .onStart { println("ON START") } .shareIn( ... ) sharedFlow.test { ... } }

Slide 172

Slide 172 text

While Subscribed @Test fun `collect with while subscribed strategy`() = runBlockingTest { 
 val sharedFlow = flow .onStart { println("ON START") } .shareIn( ... ) sharedFlow.test { ... } }

Slide 173

Slide 173 text

While Subscribed ● Start upstream flow upon subscriber ● Remain active while there are subscribers

Slide 174

Slide 174 text

Eagerly ● Start upstream flow upon creation

Slide 175

Slide 175 text

Eagerly @Test fun `collect with eagerly strategy`() = runBlockingTest { 
 val sharedFlow = flow .onStart { println("ON START") } .shareIn( sharingScope, SharingStarted.Eagerly, 1 ) }

Slide 176

Slide 176 text

Eagerly @Test fun `collect with eagerly strategy`() = runBlockingTest { 
 val sharedFlow = flow .onStart { println("ON START") } .shareIn( sharingScope, SharingStarted.Eagerly, 1 ) } Will start

Slide 177

Slide 177 text

Lazily ● Start upstream flow upon subscription ● Never stops upstream

Slide 178

Slide 178 text

Lazily @Test fun `collect with lazily strategy`() = runBlockingTest { 
 val sharedFlow = flow .onStart { println("ON START") } .shareIn( sharingScope, SharingStarted.Lazily, 1 ) } Will start

Slide 179

Slide 179 text

Lazily @Test fun `collect with lazily strategy`() = runBlockingTest { 
 val sharedFlow = flow .onComplete { println("ON Complete”) } .shareIn( sharingScope, SharingStarted.Lazily, 1 ) }

Slide 180

Slide 180 text

Lazily ● Start upstream flow upon subscription ● Never stops upstream

Slide 181

Slide 181 text

Converting to Shared Flows ● While Subscribed ● Eagerly ● Lazily

Slide 182

Slide 182 text

https: // codingwithmohit.com/coroutines/learning-shared-and-state-flows-with-tests/ Coding with Mohit

Slide 183

Slide 183 text

Thank You! www.codingwithmohit.com @heyitsmohit