Testing Work
The work-testing
artifact offers a WorkManagerTestInitHelper
utility class to help with instrumented testing.
First, it has initializeTestWorkManager()
. This configures WorkManager
to use a SynchronousExecutor
. This amounts to a mock Executor
, one that runs supplied Runnable
objects immediately on the current thread. By using SynchronousExecutor
, your enqueue()
calls for WorkManager
will happen immediately and synchronously, rather than asynchronously.
Also, WorkManagerTestInitHelper
has a getTestDriver()
method, which returns a TestDriver
. This offers a setAllConstraintsMet()
method, which takes a work request ID and tells WorkManager
that all of the constraints for that work request are met. This makes your tests more deterministic, since constraints are normally there to test the environment, and that might change from run to run of your tests. However, it is very important to call setAllConstraintsMet()
after you enqueue()
the work:
WorkManager.getInstance(context).enqueue(work);
WorkManagerTestInitHelper.getTestDriver(context).setAllConstraintsMet(work.getId());
Note: calling setAllConstraintsMet()
before calling enqueue()
results in a crash.
The Work/Download
sample app contains a DownloadWorkerTest
class that shows the use of WorkManagerTestInitHelper
and TestDriver
:
package com.commonsware.jetpack.work.download;
import android.content.Context;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.File;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.work.Constraints;
import androidx.work.Data;
import androidx.work.NetworkType;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
import androidx.work.WorkRequest;
import androidx.work.testing.WorkManagerTestInitHelper;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@RunWith(AndroidJUnit4.class)
public class DownloadWorkerTest {
private File expected;
private final Context context =
InstrumentationRegistry.getInstrumentation().getTargetContext();
@Before
public void setUp() {
WorkManagerTestInitHelper.initializeTestWorkManager(context);
expected = new File(context.getCacheDir(), "oldbook.pdf");
if (expected.exists()) {
expected.delete();
}
}
@Test
public void download() {
assertFalse(expected.exists());
WorkManager.getInstance(context).enqueue(buildWorkRequest(null));
assertTrue(expected.exists());
}
@Test
public void downloadWithConstraints() {
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.build();
WorkRequest work = buildWorkRequest(constraints);
assertFalse(expected.exists());
WorkManager.getInstance(context).enqueue(work);
WorkManagerTestInitHelper.getTestDriver(context)
.setAllConstraintsMet(work.getId());
assertTrue(expected.exists());
}
private WorkRequest buildWorkRequest(Constraints constraints) {
OneTimeWorkRequest.Builder builder =
new OneTimeWorkRequest.Builder(DownloadWorker.class)
.setInputData(new Data.Builder()
.putString(DownloadWorker.KEY_URL,
"https://commonsware.com/Android/Android-1_0-CC.pdf")
.putString(DownloadWorker.KEY_FILENAME, "oldbook.pdf")
.build())
.addTag("download");
if (constraints != null) {
builder.setConstraints(constraints);
}
return builder.build();
}
}
package com.commonsware.jetpack.work.download
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import androidx.work.*
import androidx.work.testing.WorkManagerTestInitHelper
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import java.io.File
@RunWith(AndroidJUnit4::class)
class DownloadWorkerTest {
private lateinit var expected: File
private val context =
InstrumentationRegistry.getInstrumentation().targetContext
@Before
fun setUp() {
WorkManagerTestInitHelper.initializeTestWorkManager(context)
expected = File(context.cacheDir, "oldbook.pdf")
if (expected.exists()) {
expected.delete()
}
}
@Test
fun download() {
assertFalse(expected.exists())
WorkManager.getInstance(context).enqueue(buildWorkRequest(null))
assertTrue(expected.exists())
}
@Test
fun downloadWithConstraints() {
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.build()
val work = buildWorkRequest(constraints)
assertFalse(expected.exists())
WorkManager.getInstance(context).enqueue(work)
WorkManagerTestInitHelper.getTestDriver(context)!!.setAllConstraintsMet(work.id)
assertTrue(expected.exists())
}
private fun buildWorkRequest(constraints: Constraints?): WorkRequest {
val builder = OneTimeWorkRequest.Builder(DownloadWorker::class.java)
.setInputData(
Data.Builder()
.putString(
DownloadWorker.KEY_URL,
"https://commonsware.com/Android/Android-1_0-CC.pdf"
)
.putString(DownloadWorker.KEY_FILENAME, "oldbook.pdf")
.build()
)
.addTag("download")
if (constraints != null) {
builder.setConstraints(constraints)
}
return builder.build()
}
}
This class tests DownloadWorker
both with and without constraints, validating that the output file exists after the work has been done. Since we are using the synchronous test configuration of WorkManager
, we can test this work without having to resort to CountDownLatch
or similar tricks for testing multithreaded code.
Independent of work-testing
, note that Worker
has some dependencies on Context
, and it may be difficult to mock that Context
since you are not the one providing it. It may be necessary to consider your Worker
as something to be tested with instrumented tests, as we are doing here. If you wish to have deferred tasks be unit tested outside of Android, consider isolating that logic in another class that your Worker
then happens to use.
Prev Table of Contents Next
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.