|
1 | | -# RxJava-Style-LiveData-TestObserver |
| 1 | +# RxJavaStyleLiveData and Flow TestObserver |
2 | 2 | TestObserver class for LiveData to test multiple values like ViewState such as loading, and result states or multiple post and setValues |
3 | 3 |
|
| 4 | +## LiveData |
4 | 5 |
|
5 | 6 | ### Implementation |
6 | 7 |
|
@@ -102,3 +103,169 @@ fun test() { |
102 | 103 | } |
103 | 104 | |
104 | 105 | ``` |
| 106 | + |
| 107 | +## Flow |
| 108 | + |
| 109 | +### Implementation |
| 110 | + |
| 111 | +``` |
| 112 | + |
| 113 | +class FlowTestObserver<T>( |
| 114 | + coroutineScope: CoroutineScope, |
| 115 | + private val flow: Flow<T> |
| 116 | +) { |
| 117 | + private val testValues = mutableListOf<T>() |
| 118 | + private var error: Throwable? = null |
| 119 | + |
| 120 | +// private val job: Job = coroutineScope.launch { |
| 121 | +// flow |
| 122 | +// .catch { throwable -> |
| 123 | +// error = throwable |
| 124 | +// } |
| 125 | +// .collect { |
| 126 | +// testValues.add(it) |
| 127 | +// } |
| 128 | +// } |
| 129 | + |
| 130 | + private val job: Job = flow |
| 131 | + .catch { throwable -> |
| 132 | + error = throwable |
| 133 | + } |
| 134 | + .onEach { testValues.add(it) } |
| 135 | + .launchIn(scope = coroutineScope) |
| 136 | + |
| 137 | + |
| 138 | + fun assertNoValues(): FlowTestObserver<T> { |
| 139 | + if (testValues.isNotEmpty()) throw AssertionError( |
| 140 | + "Assertion error! Actual size ${testValues.size}" |
| 141 | + ) |
| 142 | + return this |
| 143 | + } |
| 144 | + |
| 145 | + fun assertValueCount(count: Int): FlowTestObserver<T> { |
| 146 | + if (count < 0) throw AssertionError( |
| 147 | + "Assertion error! Value count cannot be smaller than zero" |
| 148 | + ) |
| 149 | + if (count != testValues.size) throw AssertionError( |
| 150 | + "Assertion error! Expected $count while actual ${testValues.size}" |
| 151 | + ) |
| 152 | + return this |
| 153 | + } |
| 154 | + |
| 155 | + fun assertValues(vararg values: T): FlowTestObserver<T> { |
| 156 | + if (!testValues.containsAll(values.asList())) |
| 157 | + throw AssertionError("Assertion error! At least one value does not match") |
| 158 | + return this |
| 159 | + } |
| 160 | + |
| 161 | +// fun assertValues(predicate: List<T>.() -> Boolean): FlowTestObserver<T> { |
| 162 | +// if (!testValues.predicate()) |
| 163 | +// throw AssertionError("Assertion error! At least one value does not match") |
| 164 | +// return this |
| 165 | +// } |
| 166 | +// |
| 167 | +// fun values(predicate: List<T>.() -> Unit): FlowTestObserver<T> { |
| 168 | +// testValues.predicate() |
| 169 | +// return this |
| 170 | +// } |
| 171 | + |
| 172 | + fun assertValues(predicate: (List<T>) -> Boolean): FlowTestObserver<T> { |
| 173 | + if (!predicate(testValues)) |
| 174 | + throw AssertionError("Assertion error! At least one value does not match") |
| 175 | + return this |
| 176 | + } |
| 177 | + |
| 178 | + fun assertError(throwable: Throwable): FlowTestObserver<T> { |
| 179 | + |
| 180 | + val errorNotNull = exceptionNotNull() |
| 181 | + |
| 182 | + if (!(errorNotNull::class.java == throwable::class.java && |
| 183 | + errorNotNull.message == throwable.message) |
| 184 | + ) |
| 185 | + throw AssertionError("Assertion Error! throwable: $throwable does not match $errorNotNull") |
| 186 | + |
| 187 | + return this |
| 188 | + } |
| 189 | + |
| 190 | + fun assertError(errorClass: Class<Throwable>): FlowTestObserver<T> { |
| 191 | + |
| 192 | + val errorNotNull = exceptionNotNull() |
| 193 | + |
| 194 | + if (errorNotNull::class.java != errorClass) |
| 195 | + throw AssertionError("Assertion Error! errorClass $errorClass does not match ${errorNotNull::class.java}") |
| 196 | + |
| 197 | + return this |
| 198 | + } |
| 199 | + |
| 200 | + fun assertError(predicate: (Throwable) -> Boolean): FlowTestObserver<T> { |
| 201 | + |
| 202 | + val errorNotNull = exceptionNotNull() |
| 203 | + |
| 204 | + if (!predicate(errorNotNull)) |
| 205 | + throw AssertionError("Assertion Error! Exception for $errorNotNull") |
| 206 | + |
| 207 | + return this |
| 208 | + } |
| 209 | + |
| 210 | + fun assertNull(): FlowTestObserver<T> { |
| 211 | + |
| 212 | + testValues.forEach { |
| 213 | + if (it != null) throw AssertionError("Assertion Error! There are more than one item that is not nuşş") |
| 214 | + |
| 215 | + } |
| 216 | + |
| 217 | + return this |
| 218 | + } |
| 219 | + |
| 220 | + fun values(predicate: (List<T>) -> Unit): FlowTestObserver<T> { |
| 221 | + predicate(testValues) |
| 222 | + return this |
| 223 | + } |
| 224 | + |
| 225 | + fun values(): List<T> { |
| 226 | + return testValues |
| 227 | + } |
| 228 | + |
| 229 | + private fun exceptionNotNull(): Throwable { |
| 230 | + |
| 231 | + if (error == null) |
| 232 | + throw AssertionError("There is no exception") |
| 233 | + |
| 234 | + return error!! |
| 235 | + } |
| 236 | + |
| 237 | + fun dispose() { |
| 238 | + job.cancel() |
| 239 | + } |
| 240 | +} |
| 241 | + |
| 242 | +fun <T> Flow<T>.test(scope: CoroutineScope): FlowTestObserver<T> { |
| 243 | + return FlowTestObserver(scope, this) |
| 244 | +} |
| 245 | + |
| 246 | +``` |
| 247 | + |
| 248 | +## Usage |
| 249 | +``` |
| 250 | + postRemoteRepository.getPostFlow() |
| 251 | + .test(testCoroutineScope) |
| 252 | +// .assertError(Exception("Network Exception")) |
| 253 | + .assertError { |
| 254 | + it.message == "Network Exception" |
| 255 | + } |
| 256 | + .dispose() |
| 257 | + |
| 258 | + |
| 259 | + testCoroutineScope.runBlockingTest { |
| 260 | + |
| 261 | + // GIVEN |
| 262 | + every { postDBRepository.getPostListFlow() } returns flow { emit(listOf()) } |
| 263 | + |
| 264 | + // WHEN |
| 265 | + val testObserver = postDBRepository.getPostListFlow().test(this) |
| 266 | + |
| 267 | + // THEN |
| 268 | + val actual = testObserver.values()[0] |
| 269 | + Truth.assertThat(actual.size).isEqualTo(0) |
| 270 | + testObserver.dispose() |
| 271 | + } |
0 commit comments