Testing RxWorker for WorkManager - android

I want to try out work manager for the first time. I am used to rxJava so I decided to implement my work manager using RxWorker. But the testing aspect is giving me headache.Basically, the work manager checks firebase to get latest changes to latest changes to particular document (This is not the best use case I know).But the problem is in the test returns without waiting for success or failure.It returns when the work manager is still running.
This is my work manager implementation
class MidiSyncWorker(context: Context, params: WorkerParameters) : RxWorker(context, params) {
override fun createWork(): Single<Result> {
return Injection.provideSharePrefsRepo.midiArchiveVersion()
.flatMapObservable { currentVersion ->
Injection.provideOnlineRepo.latestMidiArchive()
.filter { onlineMidi -> onlineMidi.version > currentVersion }
}.firstOrError()
.map { onlineMidi ->
val outputData = Data.Builder()
.putString(KEY_FIREBASE_ARCHIVE_PATH, onlineMidi.url)
Result.success(outputData.build()) }
}
.onErrorReturn { Result.failure() }
}
This is my test
fun midiSyncVersionCheck_success_onlineVersionDiffersFromLocalVersion() {
// create request
val request = OneTimeWorkRequestBuilder<MidiSyncWorker>()
.build()
wmRule.workManager.enqueue(request).result.get()
val workInfo = wmRule.workManager.getWorkInfoById(request.id).get(10, TimeUnit.SECONDS)
assertThat(workInfo.state, `is`(WorkInfo.State.SUCCEEDED))
}
I expected the test to wait until workmanager returns success or failure. But it returns while work manager is still running
java.lang.AssertionError:
Expected: is <SUCCEEDED>
but: was <RUNNING>

WorkManager makes available a couple of ways to test your Worker classes. You can find all the details on WorkManager Testing documentation page.
The original WorkManagerTestInitHelper only supports Worker classes, meanwhile, the newly introduce in (WorkManager v2.1.0-alpha01) TestListenableWorkerBuilder can be used to test both ListenableWorker classes and the other classes that derives from it (like CoroutineWorker and RxWorker.
In your particular case, you should be able to do:
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.work.ListenableWorker.Result
import androidx.work.testing.TestListenableWorkerBuilder
import org.hamcrest.CoreMatchers.`is`
import org.junit.Assert.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
#RunWith(JUnit4::class)
class MyWorkTest {
private lateinit var context: Context
#Before
fun setup() {
context = ApplicationProvider.getApplicationContext()
}
#Test
fun testMidiSyncWorker() {
// Get the ListenableWorker
val worker = TestListenableWorkerBuilder<MidiSyncWorker>(context).build()
// Start the work synchronously
val result = worker.startWork().get()
assertThat(result, `is`(Result.success()))
}
}
In this way you're calling synchrously your worker class.
In this case you need to use the as a test dependency in your build.gradle file:
def work_version = "2.1.0-alpha02"
androidTestImplementation "androidx.work:work-testing:$work_version"
You can find a similar, complete, sample (for a CoroutineWorker), on the Kotlin's coroutine Codelab.

Related

How to use flowOn and launchIn methods to collect from a Kotlin SharedFlow in a test

Consider the following test. test shared flow A will pass, but test shared flow B will fail.
I was under the impression that these were equivalent statements.
Why does test shared flow B fail?
Is there a way to make it pass, while still using the launchIn method?
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Test
#OptIn(ExperimentalCoroutinesApi::class)
class SomethingTest {
#Test
fun `test shared flow A`() = runTest {
val flow = MutableSharedFlow<Int>()
val items = mutableListOf<Int>()
val job = launch(UnconfinedTestDispatcher()) {
flow.collect {
items.add(it)
}
}
flow.emit(1)
assert(items.size == 1)
job.cancel()
}
#Test
fun `test shared flow B`() = runTest {
val flow = MutableSharedFlow<Int>()
val items = mutableListOf<Int>()
val job = flow.onEach { items.add(it) }
.flowOn(UnconfinedTestDispatcher())
.launchIn(this)
flow.emit(1)
assert(items.size == 1)
job.cancel()
}
}
Neither test shared flow A nor test shared flow B is guaranteed to pass because neither test waits for launch to complete before assert.
test shared flow A happens to pass, and will fail if you slightly delay adding an item.
Is there a way to make it pass, while still using the launchIn method?
Adding advanceUntilIdle() will pass test shared flow B.
#Test
fun `test shared flow B`() = runTest {
val flow = MutableSharedFlow<Int>()
val items = mutableListOf<Int>()
val job = flow.onEach { items.add(it) }
.flowOn(UnconfinedTestDispatcher())
.launchIn(this)
advanceUntilIdle()
flow.emit(1)
assert(items.size == 1)
job.cancel()
}

Android Kotlin: Periodic work manager is not running the function periodically in the background

I am building an Android app using Kotlin and Android Studio. I am trying to run a background task even when the app is dead or completely closed. I am using WorkManager. But it seems to be running just once.
This is my worker class.
import android.content.Context
import android.util.Log
import androidx.work.Worker
import androidx.work.WorkerParameters
class RegisterReceiversWorker(context: Context, workerParams: WorkerParameters): Worker(context, workerParams) {
companion object {
const val WORK_NAME: String = "REGISTER_RECEIVERS_WORKER"
}
override fun doWork(): Result {
registerReceivers()
return Result.success();
}
private fun registerReceivers() {
Log.i("REGISTER_RECEIVER", "Greeting from the background")
}
}
As you can see in the code, I am printing out a message in the logcat.
Then I start the worker as follow in the MainActivity when the app is first opened.
val workRequest = PeriodicWorkRequestBuilder<RegisterReceiversWorker>(10, TimeUnit.SECONDS).build()
val workManager = WorkManager.getInstance(applicationContext)
workManager.enqueueUniquePeriodicWork(RegisterReceiversWorker.WORK_NAME, ExistingPeriodicWorkPolicy.REPLACE, workRequest);
As you can see, it should print out the message every ten seconds. But when I run the app, it is printing out the message in the logcat, just once. What is wrong with my code? How can I make sure that it is running every 10 seconds in the background even when the app is closed?
Periodic work needs to be scheduled in an interval longer than MIN_PERIODIC_INTERVAL_MILLIS.

Unit testing to check that the accept was called when using RxBindings

AS 4.0.2
RxBindings 4.0.0
I have written a unit test for testing if the PublishRelay accept has been called that uses jakeWartons rxBindings
This is the snippet of code I am testing
private val checkStoresRelay: Relay<Unit> = PublishRelay.create<Unit>()
private fun onCheckStockTapped(view: View) {
view.textViewCheckStock
.clicks()
.debounce(TIME_OUT, TimeUnit.MILLISECONDS)
.subscribeBy(
onNext = checkStoresRelay::accept,
onError = { Timber.e(it, it.localizedMessage) }
)
}
In the test class I am doing the following. The textViewCheckStock.performClick() will trigger and I check that the checkStoresRelay.accept(Unit) had been called.
But I am not sure about the availableDeliveryOptionsModel.checkStoresRelay.accept(Unit) As I don't think I should be calling this in the test. However, the test will fail if I remove this. Just wondering what would be the best way to test this?
#Test
fun `should check stores when checkStock is tapped`() {
// Arrange
val viewHolder = createViewHolder()
availableDeliveryOptionsModel.bind(viewHolder)
val actual = availableDeliveryOptionsModel.checkStoresRelay.test()
// Act
availableDeliveryOptionsModel.checkStoresRelay.accept(Unit)
viewHolder.itemView.textViewCheckStock.performClick()
// Assert
actual.assertValue(Unit)
}
Many thanks for any suggestions,
You definitely shouldn't call relay.accept() directly within your test. accept should be called on your behalf in subscribeBy(). The problem is based on .debounce() operator.
Debounce waits for a given amount of time and if there isn't subsequent emit (view click), it emits item downstream (into .subscribeBy()). This creates some delay and results in a test failure, because actual.assertValue(Unit) is called before click (emit) is delivered.
You can solve this issue with RxSchedulerRule. Execution of debounce operator is done immediately on the current thread, so you'll basically handle item delivery to relay and run assertions after.
I have simplified your example a little bit, but I hope the main point remains:
class SO64328289 {
val checkStoresRelay: Relay<Unit> = PublishRelay.create()
fun onCheckStockTapped(view: View) {
view.clicks()
.debounce(1, TimeUnit.MILLISECONDS)
.subscribeBy(
onNext = checkStoresRelay::accept,
onError = { Log.e("SO64328289", it.localizedMessage) }
)
}
}
#RunWith(RobolectricTestRunner::class)
#Config(sdk = [28])
class SO64328289Test {
#get:Rule val schedulerRule = RxSchedulerRule()
#Test
fun `when button clicked then relay emits value`() {
val tested = SO64328289()
val view = View(ApplicationProvider.getApplicationContext())
tested.onCheckStockTapped(view)
val relayTestSubscription = tested.checkStoresRelay.test()
view.performClick()
relayTestSubscription.assertValue(Unit)
}
}
I have used Robolectric, but it should also work with other testing frameworks. These are dependencies I have used in my test project if you want to try it for yourself:
implementation 'com.jakewharton.rxbinding3:rxbinding:3.1.0'
implementation 'com.jakewharton.rxrelay2:rxrelay:2.1.1'
implementation "io.reactivex.rxjava2:rxkotlin:2.4.0"
implementation "io.reactivex.rxjava2:rxjava:2.2.20"
testImplementation 'com.github.Plastix.RxSchedulerRule:rx2:1.0.2' // requires jitpack.io maven
testImplementation 'org.robolectric:robolectric:4.4'
testImplementation 'junit:junit:4.13.1'
testImplementation 'androidx.test:core:1.3.0'

RxJava Single filtering a list inside an object and return its object with the filtered list

i'm using RxJava2 (Single) with retrofit for network requests, when receiving the response, amog other fields that the object contains, theres a list of objects, what im trying achieve is to filter out the objects(in the list) that contains a particular 'id'
and I want it to happen on the background thread of course, and then emit response after the object has its list filtered.
out of scope
is there a way I can detect in each operator which thread its using ?
Prerequisite
implementation 'io.reactivex.rxjava2:rxjava:2.2.19'
implementation("io.reactivex.rxjava2:rxandroid:2.1.1")
API
data class Result(val id: Int)
data class MyObject(val values: List<Result> = emptyList())
interface RetroFitApi {
fun getAll(): Single<MyObject>
}
internal class RetroFitApiImpl : RetroFitApi {
override fun getAll(): Single<MyObject> {
return Single.fromCallable {
MyObject(
listOf(Result(1), Result(2), Result(3))
)
}
}
}
Retrofit usage, when Retrofit has no own threading model (androidTest)
import android.os.Looper
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import java.util.concurrent.TimeUnit
#Test
internal fun name() {
val api = RetroFitApiImpl()
val test = api.getAll()
// make sure the subscribe lambda is called in background-thread
.subscribeOn(Schedulers.io())
.map { result ->
// remove all elements, which are id == 1
result.copy(values = result.values.filterNot { it.id == 1 })
}
// move emit, which will probably be emitted from Schedules#io-Thread to Main-Loop. Therefore after applying observeOn the onNext emit in subscribe will be emitted on the UI-Android-Loop
.observeOn(AndroidSchedulers.mainThread())
.test()
test.awaitDone(500, TimeUnit.MILLISECONDS)
assertThat(test.lastThread()).isEqualTo(Looper.getMainLooper().thread)
assertThat(test.values()).containsExactly(
MyObject(values = listOf(Result(2), Result(3)))
)
}
Regarding
out of scope is there a way I can detect in each operator which thread its using ?
You can only test for it with a TestConsumer (Single#test), wihch Thread was last called. You can not know on which Thread an onNext will be emitted during runtime, because RxJava does not care about threading at all, only if you take it into your own hands with observeOn/ subscribeOn. By default onNext will be called on the calling-thread. If the calling thread, is als the subscribing one, your result will (probably) be emitted synchronous, if no threading is involved.
Further reading:
http://tomstechnicalblog.blogspot.com/2016/02/rxjava-understanding-observeon-and.html

How to test Evernote's Android Jobs?

How do I test Jobs created with Android-Job library? Any ideas on unit testing, instrumented testing or even manual testing are appreciated, I just want to check if it works as expected.
To be specific, I have a job that performs an HTTP request every N hours:
package com.kondenko.yamblzweather.job;
import android.support.annotation.NonNull;
import com.evernote.android.job.Job;
import com.evernote.android.job.JobRequest;
import com.kondenko.yamblzweather.model.entity.WeatherData;
import com.kondenko.yamblzweather.ui.weather.WeatherInteractor;
import com.kondenko.yamblzweather.utils.SettingsManager;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
public class UpdateWeatherJob extends Job {
private WeatherInteractor interactor;
private String cityId;
private String units;
private long refreshRateHr;
// Do not delete, needed for job creation
public static final String TAG = "UpdateWeaterJob";
#Inject
public UpdateWeatherJob(WeatherInteractor interactor, SettingsManager settingsManager) {
this.interactor = interactor;
this.cityId = settingsManager.getSelectedCity();
this.units = settingsManager.getSelectedUnitValue();
this.refreshRateHr = settingsManager.getRefreshRate();
}
#NonNull
#Override
protected Result onRunJob(Params params) {
WeatherData data = interactor.getWeather(cityId, units).blockingGet();
return data != null ? Result.SUCCESS : Result.FAILURE;
}
public void buildJobRequest(String name) {
new JobRequest.Builder(UpdateWeatherJob.TAG)
.setPeriodic(TimeUnit.HOURS.toMillis(refreshRateHr))
.setRequiredNetworkType(JobRequest.NetworkType.CONNECTED)
.setRequirementsEnforced(true)
.setPersisted(true)
.build()
.schedule();
}
}
We at Evernote test jobs the following way:
Unit tests -> We tend to extract the logic into actions, similar to presenters in a MVP setup. This removes Android dependencies and the actions are unit testable.
QA -> We have QA options to trigger jobs manually. This way our QA team can verify that the job produces the correct output
Verifying timing -> There we rely on the logs.
You also should take a look at these slides. ADB can be really helpful to verify curtain assumptions.

Categories

Resources