How to execute a defined function after each function in Kotlin - kotlin

I am writing Espresso unit test code.
What I want to do is taking screenshot on every actions without specifying
takeSpoonScreenshot("")
This is my AndroidJUnit4 Testcode:
#Test
fun givenVideoDetail_whenChooseCurrentItem_thenShowCountLabel() {
pickerPage {
clickFirstVideoItem()
}
videoDetailPage {
clickSelectCheckBox()
assertCountLabel()
}
}
and this is my VideoDetailPage.kt:
fun videoDetailPage(func: VideoDetailPage.() -> Unit) = VideoDetailPage.apply {
assertFirstPage()
func()
}
fun screenshotAfterAction(func: VideoDetailPage.() -> Unit) = VideoDetailPage.apply {
func()
takeSpoonScreenshot("")
}
object VideoDetailPage : BaseActions() {
// Write 'How to test' here
fun assertFirstPage() {
resourceIsDisplayed(R.id.send_balloon_image)
resourceIsDisplayed(R.id.media_detail_item_check_box)
resourceIsDisplayed(R.id.video_editor_mute_btn)
}
fun clickFilterButton() = takeScreenshotAfterFunction {
clickButton(R.id.image_editor_filter)
}
fun clickSelectCheckBox() {
clickButton(R.id.media_detail_item_check_box)
}
fun assertFilterSelectionListIsOpen() {
resourceIsDisplayed(R.id.media_filter_list)
}
fun assertCountLabel() {
resourceIsDisplayed(R.id.media_editor_selected_count)
}
}
See that I made takeScreenshotAfterFunction, but It is not proper because I should write takeScreenshotAfterFunction N times.

Related

Builder pattern with infix for Coroutines

I am trying to write a class to easily chain code run in different coroutine contexts.
Ideally, I would like to use it like this:
io {
// Run IO code that returns an object (nullable)
} ui { ioResult->
// Run UI code using the returned object (non-nullable)
} ifNull {
// Run UI code when the returned object is null
}
What I have so far works like this:
GlobalScope.launch {
CoroutineLinker(null).io {
// Run IO code
} ui { ioResult ->
ioResult?.also {
// Run UI code after null check
} ?: run {
// Run UI code when null
}
} ifNull {
// Redundant block
}
}
As you can see there is still quite some work left but I am stuck, so I share this with you:
class CoroutineLinker<T> (
private val value: T?
) {
suspend infix fun <K> io (block: suspend () -> K?): CoroutineLinker<K?> {
return withContext(Dispatchers.IO) {
CoroutineLinker(block())
}
}
suspend infix fun ui (block: suspend (value: T) -> Unit): CoroutineLinker<T> {
return withContext(Dispatchers.Main) {
if (value != null ) {
block(value)
}
this#CoroutineLinker
}
}
suspend infix fun ifNull (block: suspend () -> Unit) {
return withContext(Dispatchers.Main) {
if (value == null) {
block()
}
}
}
}
Any input is welcome! :)
I think this will do what you need:
suspend fun <K : Any> io (block: suspend () -> K?) = CoroutineLinker(null).io(block)
class CoroutineLinker<T : Any> (
private val value: T?
) {
suspend infix fun <K : Any> io (block: suspend () -> K?): CoroutineLinker<K> {
return withContext(Dispatchers.IO) {
CoroutineLinker(block())
}
}
suspend infix fun ui (block: suspend (value: T) -> Unit): CoroutineLinker<T> {
if (value != null ) {
withContext(Dispatchers.Main) {
block(value)
}
}
return this
}
suspend infix fun ifNull (block: suspend () -> Unit) {
if (value == null) {
withContext(Dispatchers.Main) {
block()
}
}
}
}
I changed 3 things:
Added upper bounds for CoroutineLinker to Any.
Added io function.
Changed the order of if and withContext in both functions - this is just optimization, it wasn't required.

How to change my helper function so that is collects the results of the parallel processing tasks

I wrote this helper function, so that I can easily process a list in parallel and only continue code execution when all the work is done. It works nicely when you don't need to return a result.
(I know it isn't the best practice to create new pools every time, it can be easily moved out, but I wanted to keep the examples simple.)
fun recursiveAction(action: () -> Unit): RecursiveAction {
return object : RecursiveAction() {
override fun compute() {
action()
}
}
}
fun <T> List<T>.parallelForEach(parallelSize: Int, action: (T) -> Unit) {
ForkJoinPool(parallelSize).invoke(recursiveAction {
this.parallelStream().forEach { action(it) }
})
}
Example use:
val myList: List<SomeClass> [...]
val parallelSize: Int = 8
myList.parallelForEach(parallelSize) { listElement ->
//Some task here
}
Is there any way to make a similar helper construct for when you want to collect the results back into a list?
I know I have to use a RecursiveTask instead of the RecursiveAction, but I couldn't manage to write a helper function like I had above to wrap it.
I'd like to use it like this:
val myList: List<SomeClass> [...]
val parallelSize: Int = 8
val result: List<SomeClass> = myList.parallelForEach(parallelSize) { listElement ->
//Some task here
}
Alternatively, is there a simpler way to do this alltogether?
Answered by JeffMurdock over on Reddit
fun <T> recursiveTask(action: () -> T): RecursiveTask<T> {
return object : RecursiveTask<T>() {
override fun compute(): T {
return action()
}
}
}
fun <T, E> List<T>.parallelForEach(parallelSize: Int, action: (T) -> E): List<E> {
val pool = ForkJoinPool(parallelSize)
val result = mutableListOf<ForkJoinTask<E>>()
for (item in this) {
result.add(pool.submit(recursiveTask {
action(item)
}))
}
return result.map { it.join() }
}
fun main(args: Array<String>) {
val list = listOf(1, 2, 3)
list.parallelForEach(3) { it + 2 }.forEach { println(it) }
}

Kotlin Coroutine Unit Test Flow collection with viewModelScope

I want to test a method of my ViewModel that collects a Flow. Inside the collector a LiveData object is mutated, which I want to check in the end. This is roughly how the setup looks:
//Outside viewmodel
val f = flow { emit("Test") }.flowOn(Dispatchers.IO)
//Inside viewmodel
val liveData = MutableLiveData<String>()
fun action() {
viewModelScope.launch { privateAction() }
}
suspend fun privateAction() {
f.collect {
liveData.value = it
}
}
When I now call the action() method in my unit test, the test finishes before the flow is collected. This is how the test might look:
#Test
fun example() = runBlockingTest {
viewModel.action()
assertEquals(viewModel.liveData.value, "Test")
}
I am using the TestCoroutineDispatcher via this Junit5 extension and also the instant executor extension for LiveData:
class TestCoroutineDispatcherExtension : BeforeEachCallback, AfterEachCallback, ParameterResolver {
#SuppressLint("NewApi") // Only used in unit tests
override fun supportsParameter(parameterContext: ParameterContext?, extensionContext: ExtensionContext?): Boolean {
return parameterContext?.parameter?.type === testDispatcher.javaClass
}
override fun resolveParameter(parameterContext: ParameterContext?, extensionContext: ExtensionContext?): Any {
return testDispatcher
}
private val testDispatcher = TestCoroutineDispatcher()
override fun beforeEach(context: ExtensionContext?) {
Dispatchers.setMain(testDispatcher)
}
override fun afterEach(context: ExtensionContext?) {
Dispatchers.resetMain()
testDispatcher.cleanupTestCoroutines()
}
}
class InstantExecutorExtension : BeforeEachCallback, AfterEachCallback {
override fun beforeEach(context: ExtensionContext?) {
ArchTaskExecutor.getInstance()
.setDelegate(object : TaskExecutor() {
override fun executeOnDiskIO(runnable: Runnable) = runnable.run()
override fun postToMainThread(runnable: Runnable) = runnable.run()
override fun isMainThread(): Boolean = true
})
}
override fun afterEach(context: ExtensionContext?) {
ArchTaskExecutor.getInstance().setDelegate(null)
}
}
You can try either,
fun action() = viewModelScope.launch { privateAction() }
suspend fun privateAction() {
f.collect {
liveData.value = it
}
}
#Test
fun example() = runBlockingTest {
viewModel.action().join()
assertEquals(viewModel.liveData.value, "Test")
}
or
fun action() {
viewModelScope.launch { privateAction()
}
suspend fun privateAction() {
f.collect {
liveData.value = it
}
}
#Test
fun example() = runBlockingTest {
viewModel.action()
viewModel.viewModelScope.coroutineContext[Job]!!.join()
assertEquals(viewModel.liveData.value, "Test")
}
You could also try this,
suspend fun <T> LiveData<T>.awaitValue(): T? {
return suspendCoroutine { cont ->
val observer = object : Observer<T> {
override fun onChanged(t: T?) {
removeObserver(this)
cont.resume(t)
}
}
observeForever(observer)
}
}
#Test
fun example() = runBlockingTest {
viewModel.action()
assertEquals(viewModel.liveData.awaitValue(), "Test")
}
So what I ended up doing is just passing the Dispatcher to the viewmodel constructor:
class MyViewModel(..., private val dispatcher = Dispatchers.Main)
and then using it like this:
viewModelScope.launch(dispatcher) {}
So now I can override this when I instantiate the ViewModel in my test with a TestCoroutineDispatcher and then advance the time, use testCoroutineDispatcher.runBlockingTest {}, etc.

Cannot test callbacks with mockk: invoke(any())) was not called

Given
typealias MyCallback = (s: String) -> Unit
object Hello {
fun main() {
blah { print(it) }
}
fun blah(cb: MyCallback) {
cb.invoke("hi")
}
}
Or
interface MyCallback {
fun invoke (s: String) {}
}
object Hello {
fun main() {
blah(object : MyCallback {
override fun invoke(s: String) {
print(s)
}
})
}
fun blah(cb: MyCallback) {
cb.invoke("hi")
}
}
With both I get the above error (Verification failed: call 1 of 1: MyCallback(#2).invoke(any())) was not called) when doing
#Test
fun testInvoke() {
val mock = mockk<Hello>(relaxed = true)
val cb = mockk<MyCallback>()
mock.blah(cb)
verify { cb.invoke(any()) }
}
how to fix it?
This worked for me. The Hello object doesn't need to be mocked since it is the class being tested. By mocking it, the test was only recording invocations of blah() rather than actually executing them.
Using spyk rather than mockk allows the MyCallback type to be constructed allowing the invoke() function to be defined. So maybe that's more of a workaround than an explanation of why mockk doesn't seem to retain that type info.
typealias MyCallback = (s: String) -> Unit
object Hello {
fun main() {
blah { print(it) }
}
fun blah(cb: MyCallback) {
cb.invoke("hi")
}
}
class MockKTest {
#Test
fun testInvoke() {
val mock = spyk<Hello>()
val cb = mockk<MyCallback>(relaxed = true)
mock.blah(cb) // or just do Hello.blah(cb)
verify { cb.invoke(any()) }
}
}

Single with flowable?

Try in rxJava2 Kotlin combine Single with Flowable but nothing not happening:
Does not undrstand what wrong
Flowable.create<Int>({ emmit ->
loadNewListener = object :Listener {
override fun onEmit(id: Int) {
emmit.onNext(id)
}
}
}, BackpressureStrategy.LATEST)
.debounce(500, TimeUnit.MILLISECONDS)
.flatMapSingle {
loadNew(id = it.id)
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ (data:Data) ->
}, {
Timber.e("Failed load data ${it.message}")
})
my method is returning Single:
private fun loadNew(id: Int): Single<Data> {
return when (pdfType) {
CASE_0 -> {
Single.create<Data> { emmit ->
service.get("data")
.enqueue(
object : Callback<Void> {
override fun onFailure(call: Call<Void>?, t: Throwable?) {
// failure
}
override fun onResponse(call: Call<Void>?, response: Response<Void>?) {
emmit.onSuccess(it.data)
}
}
}//single
}//case_0
CASE_1 -> 1Repository.loadsome1Rx(id = id).map { it.getData() }
CASE_2 -> 2Repository.loadsom2LocalRx(id = id).map { it.getData() }
else -> {
throw java.lang.RuntimeException("$this is not available type!")
}
}
What is wrong im my code?
Need Maby call Single in Flowable subscribe() seppurate
like this?
Flowable.create<Int>({ emmit ->
loadNewListener = object :Listener {
override fun onEmit(id: Int) {
emmit.onNext(id)
}
}
}, BackpressureStrategy.LATEST)
.debounce(500, TimeUnit.MILLISECONDS)
.subscribe({
loadNew(id = it.id)
}, {
Timber.e("")
})
This code is workin but looks not simple as via combine try.
This simple example based on your code is working
var i = 0
fun foo() {
Flowable.create<Int>({ emmit ->
emmit.onNext(i)
i++
}, BackpressureStrategy.LATEST)
.debounce(500, TimeUnit.MILLISECONDS)
.flatMapSingle {
Single.create<String> { emmit ->
emmit.onSuccess("onSuccess: $it")
}
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
Log.i("RX", "Subscribe: $it")
}, {
it.printStackTrace()
})
}
Check SingleEmitter.onSuccess() and SingleEmitter.onError() is called in all cases in when (pdfType)...
As #Stas Bondar said in answer below This simple example based on your code is working!!
Problem was in loadNewListener .
It does not init in time and has null value when need. Call create Flowable on init ViewModel but loadNewListener did not have time to create when i call him from fragment.
loadNewListener = object :Listener{...}
Becuse need some time mutch for init rxJava expression!
And combine flowable with single via flatMapSingle spent more time than just call single on flowable dubscrinbe!
So use temp field:
private var temp: Temp? = null
fun load(id: Int) {
loadNewListener.apply {
when {
this != null -> load(id = id)
else -> userEmitPdfTemp = Temp(id = id)
}
}
}
Flowable.create<Data>({ emmit ->
userEmitPdfTemp?.let {id->
emmit.onNext(Data(id))
userEmitPdfTemp =null
}
loadNewListener = object :Listener {
override fun load(id: Int) {
emmit.onNext(Data(id))
}
}
}