Introduction and Setup
WorkManager provides a
work-testing artifact which helps with unit testing of your workers for
Android Instrumentation tests.
To use the work-testing artifact, you should add it as an
androidTestImplementation dependency in build.gradle. For more information
on this, look at the Declaring dependencies section in the
WorkManager release notes.
Concepts
work-testing provides a special implementation of WorkManager for test mode,
which is initialized using WorkManagerTestInitHelper.
The work-testing artifact also provides a SynchronousExecutor
which makes it easier to write tests in a synchronous manner, without having to deal with
multiple threads, locks or latches.
Here is an example on how to use all these classes together.
Kotlin
@RunWith(AndroidJUnit4::class)
class BasicInstrumentationTest {
@Before
fun setup() {
val context = InstrumentationRegistry.getTargetContext()
val config = Configuration.Builder()
// Set log level to Log.DEBUG to make it easier to debug
.setMinimumLoggingLevel(Log.DEBUG)
// Use a SynchronousExecutor here to make it easier to write tests
.setExecutor(SynchronousExecutor())
.build()
// Initialize WorkManager for instrumentation tests.
WorkManagerTestInitHelper.initializeTestWorkManager(context, config)
}
}
Java
@RunWith(AndroidJUnit4.class)
public class BasicInstrumentationTest {
@Before
public void setup() {
Context context = InstrumentationRegistry.getTargetContext();
Configuration config = new Configuration.Builder()
// Set log level to Log.DEBUG to
// make it easier to see why tests failed
.setMinimumLoggingLevel(Log.DEBUG)
// Use a SynchronousExecutor to make it easier to write tests
.setExecutor(new SynchronousExecutor())
.build();
// Initialize WorkManager for instrumentation tests.
WorkManagerTestInitHelper.initializeTestWorkManager(
context, config);
}
}
Structuring Tests
Now that WorkManager has been initialized in test mode, you are ready to test your Workers.
Let’s say you have an EchoWorker which expects some inputData,
and simply copies (echoes) its input to its outputData.
Kotlin
class EchoWorker(context: Context, parameters: WorkerParameters)
: Worker(context, parameters) {
override fun doWork(): Result {
return when(inputData.size()) {
0 -> Result.failure()
else -> Result.success(inputData)
}
}
}
Java
public class EchoWorker extends Worker {
public EchoWorker(Context context, WorkerParameters parameters) {
super(context, parameters);
}
@NonNull
@Override
public Result doWork() {
Data input = getInputData();
if (input.size() == 0) {
return Result.failure();
} else {
return Result.success(input);
}
}
}
Basic Tests
Below is an Android Instrumentation test that tests EchoWorker. The main
takeaway here is that testing EchoWorker in test mode is very similar to how
you would use EchoWorker in a real application.
Kotlin
@Test
@Throws(Exception::class)
fun testSimpleEchoWorker() {
// Define input data
val input = workDataOf(KEY_1 to 1, KEY_2 to 2)
// Create request
val request = OneTimeWorkRequestBuilder<EchoWorker>()
.setInputData(input)
.build()
val workManager = WorkManager.getInstance(applicationContext)
// Enqueue and wait for result. This also runs the Worker synchronously
// because we are using a SynchronousExecutor.
workManager.enqueue(request).result.get()
// Get WorkInfo and outputData
val workInfo = workManager.getWorkInfoById(request.id).get()
val outputData = workInfo.outputData
// Assert
assertThat(workInfo.state, `is`(WorkInfo.State.SUCCEEDED))
assertThat(outputData, `is`(input))
}
Java
@Test
public void testSimpleEchoWorker() throws Exception {
// Define input data
Data input = new Data.Builder()
.put(KEY_1, 1)
.put(KEY_2, 2)
.build();
// Create request
OneTimeWorkRequest request =
new OneTimeWorkRequest.Builder(EchoWorker.class)
.setInputData(input)
.build();
WorkManager workManager = WorkManager.getInstance(getApplicationContext());
// Enqueue and wait for result. This also runs the Worker synchronously
// because we are using a SynchronousExecutor.
workManager.enqueue(request).getResult().get();
// Get WorkInfo and outputData
WorkInfo workInfo = workManager.getWorkInfoById(request.getId()).get();
Data outputData = workInfo.getOutputData();
// Assert
assertThat(workInfo.getState(), is(WorkInfo.State.SUCCEEDED));
assertThat(outputData, is(input));
}
Let’s write another test which makes sure that when EchoWorker gets no input
data, the expected Result is a Result.failure().
Kotlin
@Test
@Throws(Exception::class)
fun testEchoWorkerNoInput() {
// Create request
val request = OneTimeWorkRequestBuilder<EchoWorker>()
.build()
val workManager = WorkManager.getInstance(applicationContext)
// Enqueue and wait for result. This also runs the Worker synchronously
// because we are using a SynchronousExecutor.
workManager.enqueue(request).result.get()
// Get WorkInfo
val workInfo = workManager.getWorkInfoById(request.id).get()
// Assert
assertThat(workInfo.state, `is`(WorkInfo.State.FAILED))
}
Java
@Test
public void testEchoWorkerNoInput() throws Exception {
// Create request
OneTimeWorkRequest request =
new OneTimeWorkRequest.Builder(EchoWorker.class)
.build();
WorkManager workManager = WorkManager.getInstance(getApplicationContext());
// Enqueue and wait for result. This also runs the Worker synchronously
// because we are using a SynchronousExecutor.
workManager.enqueue(request).getResult().get();
// Get WorkInfo
WorkInfo workInfo = workManager.getWorkInfoById(request.getId()).get();
// Assert
assertThat(workInfo.getState(), is(WorkInfo.State.FAILED));
}
Simulating constraints, delays and periodic work
WorkManagerTestInitHelper provides you with an instance of TestDriver
which can be used to simulate initialDelays, conditions where Constraints
are met for ListenableWorkers, and intervals for PeriodicWorkRequests.
Testing Initial Delays
Worker’s can have initial delays. To test EchoWorker with an initialDelay,
rather than having to wait for the initialDelay in your test, you can use the
TestDriver to mark the WorkRequests initial delay as met.
Kotlin
@Test
@Throws(Exception::class)
fun testWithInitialDelay() {
// Define input data
val input = workDataOf(KEY_1 to 1, KEY_2 to 2)
// Create request
val request = OneTimeWorkRequestBuilder<EchoWorker>()
.setInputData(input)
.setInitialDelay(10, TimeUnit.SECONDS)
.build()
val workManager = WorkManager.getInstance(getApplicationContext())
val testDriver = WorkManagerTestInitHelper.getTestDriver()
// Enqueue and wait for result.
workManager.enqueue(request).result.get()
testDriver.setInitialDelayMet(request.id)
// Get WorkInfo and outputData
val workInfo = workManager.getWorkInfoById(request.id).get()
val outputData = workInfo.outputData
// Assert
assertThat(workInfo.state, `is`(WorkInfo.State.SUCCEEDED))
assertThat(outputData, `is`(input))
}
Java
@Test
public void testWithInitialDelay() throws Exception {
// Define input data
Data input = new Data.Builder()
.put(KEY_1, 1)
.put(KEY_2, 2)
.build();
// Create request
OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(EchoWorker.class)
.setInputData(input)
.setInitialDelay(10, TimeUnit.SECONDS)
.build();
WorkManager workManager = WorkManager.getInstance(myContext);
// Get the TestDriver
TestDriver testDriver = WorkManagerTestInitHelper.getTestDriver();
// Enqueue
workManager.enqueue(request).getResult().get();
// Tells the WorkManager test framework that initial delays are now met.
testDriver.setInitialDelayMet(request.getId());
// Get WorkInfo and outputData
WorkInfo workInfo = workManager.getWorkInfoById(request.getId()).get();
Data outputData = workInfo.getOutputData();
// Assert
assertThat(workInfo.getState(), is(WorkInfo.State.SUCCEEDED));
assertThat(outputData, is(input));
}
Testing Constraints
TestDriver can also be used to mark constraints as met using
setAllConstraintsMet. Here is an example on how you can test a Worker with
constraints.
Kotlin
@Test
@Throws(Exception::class)
fun testWithConstraints() {
// Define input data
val input = workDataOf(KEY_1 to 1, KEY_2 to 2)
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
// Create request
val request = OneTimeWorkRequestBuilder<EchoWorker>()
.setInputData(input)
.setConstraints(constraints)
.build()
val workManager = WorkManager.getInstance(myContext)
val testDriver = WorkManagerTestInitHelper.getTestDriver()
// Enqueue and wait for result.
workManager.enqueue(request).result.get()
testDriver.setAllConstraintsMet(request.id)
// Get WorkInfo and outputData
val workInfo = workManager.getWorkInfoById(request.id).get()
val outputData = workInfo.outputData
// Assert
assertThat(workInfo.state, `is`(WorkInfo.State.SUCCEEDED))
assertThat(outputData, `is`(input))
}
Java
@Test
public void testWithConstraints() throws Exception {
// Define input data
Data input = new Data.Builder()
.put(KEY_1, 1)
.put(KEY_2, 2)
.build();
// Define constraints
Constraints constraints = new Constraints.Builder()
.setRequiresDeviceIdle(true)
.build();
// Create request
OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(EchoWorker.class)
.setInputData(input)
.setConstraints(constraints)
.build();
WorkManager workManager = WorkManager.getInstance(myContext);
TestDriver testDriver = WorkManagerTestInitHelper.getTestDriver();
// Enqueue
workManager.enqueue(request).getResult().get();
// Tells the testing framework that all constraints are met.
testDriver.setAllConstraintsMet(request.getId());
// Get WorkInfo and outputData
WorkInfo workInfo = workManager.getWorkInfoById(request.getId()).get();
Data outputData = workInfo.getOutputData();
// Assert
assertThat(workInfo.getState(), is(WorkInfo.State.SUCCEEDED));
assertThat(outputData, is(input));
}
Testing Periodic Work
The TestDriver also exposes a setPeriodDelayMet which can be used to
indicate that an interval is complete. Here is an example of
setPeriodDelayMet being used.
Kotlin
@Test
@Throws(Exception::class)
fun testPeriodicWork() {
// Define input data
val input = workDataOf(KEY_1 to 1, KEY_2 to 2)
// Create request
val request = PeriodicWorkRequestBuilder<EchoWorker>(15, MINUTES)
.setInputData(input)
.build()
val workManager = WorkManager.getInstance(myContext)
val testDriver = WorkManagerTestInitHelper.getTestDriver()
// Enqueue and wait for result.
workManager.enqueue(request).result.get()
// Tells the testing framework the period delay is met
testDriver.setPeriodDelayMet(request.id)
// Get WorkInfo and outputData
val workInfo = workManager.getWorkInfoById(request.id).get()
// Assert
assertThat(workInfo.state, `is`(WorkInfo.State.ENQUEUED))
}
Java
@Test
public void testPeriodicWork() throws Exception {
// Define input data
Data input = new Data.Builder()
.put(KEY_1, 1)
.put(KEY_2, 2)
.build();
// Create request
PeriodicWorkRequest request =
new PeriodicWorkRequest.Builder(EchoWorker.class, 15, MINUTES)
.setInputData(input)
.build();
WorkManager workManager = WorkManager.getInstance(myContext);
TestDriver testDriver = WorkManagerTestInitHelper.getTestDriver();
// Enqueue
workManager.enqueue(request).getResult().get();
// Tells the testing framework the period delay is met
testDriver.setPeriodDelayMet(request.getId());
// Get WorkInfo and outputData
WorkInfo workInfo = workManager.getWorkInfoById(request.getId()).get();
// Assert
assertThat(workInfo.getState(), is(WorkInfo.State.ENQUEUED));
}

