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.