Register now for Android Dev Summit 2019!

Testing

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()
    // 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();
   // 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()
   // 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();
  // 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()
    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();
  // 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()
    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();
    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()
    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();
    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));
}