使用 WorkManager 進行整合測試

WorkManager 提供 work-testing 構件,可協助您測試工作站。

設定

如要使用 work-testing 構件,請將其新增至 build.gradle 中的 androidTestImplementation 依附元件。

Groovy

dependencies {
    def work_version = "2.5.0"

    ...

    // optional - Test helpers
    androidTestImplementation "androidx.work:work-testing:$work_version"
}

Kotlin

dependencies {
    val work_version = "2.4.0"

    ...

    // optional - Test helpers
    androidTestImplementation("androidx.work:work-testing:$work_version")
}

如要進一步瞭解如何新增依附元件,請參閱 「WorkManager 版本資訊」中的「宣告依附元件」一節。

概念

work-testing 提供 WorkManager 專用實作的測試模式,這個模式會使用 WorkManagerTestInitHelper 進行初始化。

work-testing 構件也提供 SynchronousExecutor,讓您更容易以同步的方式撰寫測試,而無需處理多個執行緒或 latch。

以下示範如何搭配使用上述所有類別。

Kotlin

@RunWith(AndroidJUnit4::class)
class BasicInstrumentationTest {
    @Before
    fun setup() {
        val context = InstrumentationRegistry.getTargetContext()
        val config = Configuration.Builder()
            .setMinimumLoggingLevel(Log.DEBUG)
            .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()
                .setMinimumLoggingLevel(Log.DEBUG)
                .setExecutor(new SynchronousExecutor())
                .build();

        // Initialize WorkManager for instrumentation tests.
        WorkManagerTestInitHelper.initializeTestWorkManager(
            context, config);
    }
}

架構測試

由於 WorkManager 已在測試模式中初始化,您現在可以開始測試工作站。

假設您有一個 EchoWorker 會有一些 inputData,而只需簡單複製 (亦即 echo) 輸入內容給 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);
      }
  }
}

基本測試

以下是測試 EchoWorker 的 Android 檢測設備測試。這裡的重點在於,在測試模式中測試 EchoWorker 與在實際應用程式中使用 EchoWorker 的方式非常類似的。

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 = OneTimeWorkRequestBuilderE<choWorker(>)
        .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));
}

我們再撰寫另一項測試,確保 EchoWorker 在沒有任何輸入資料時,ResultResult.failure()

Kotlin

@Test
@Throws(Exception::class)
fun testEchoWorkerNoInput() {
   // Create request
   val request = OneTimeWorkRequestBuilderE<choWorker(>)
       .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));
}

模擬限制、延遲和週期性工作

WorkManagerTestInitHelper 會提供 TestDriver 的例項,可用於模擬初始延遲,也就是 ListenableWorker 例項符合限制的條件,以及 PeriodicWorkRequest 例項的間隔。

測試初始延遲

工作站作業可能會初始延遲。如要使用 initialDelay 測試 EchoWorker,您不必等待測試中的 initialDelay,而是使用 TestDriver 將工作要求的初始延遲時間條件,符合使用 setInitialDelayMet

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 = OneTimeWorkRequestBuilderE<choWorker(>)
        .setInputData(input)
        .setInitialDelay(10, TimeUnit.SECONDS)
        .build()

    val workManager = WorkManager.getInstance(getApplicationContext())
    // Get the TestDriver
    val testDriver = WorkManagerTestInitHelper.getTestDriver()
    // Enqueue
    workManager.enqueue(request).result.get()
    // Tells the WorkManager test framework that initial delays are now met.
    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));
}

測試限制

TestDriver 也可用來使用 setAllConstraintsMet 將限制條件標示為符合。以下範例說明如何測試設有限制的 Worker

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 = OneTimeWorkRequestBuilderE<choWorker(>)
        .setInputData(input)
        .setConstraints(constraints)
        .build()

    val workManager = WorkManager.getInstance(myContext)
    val testDriver = WorkManagerTestInitHelper.getTestDriver()
    // Enqueue
    workManager.enqueue(request).result.get()
    // Tells the testing framework that all constraints are met.
    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));
}

週期工作測試

TestDriver 也會提供 setPeriodDelayMet,並用來表示時間間隔已完成。以下是 setPeriodDelayMet 的使用範例。

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 = PeriodicWorkRequestBuilderE<choWorker(>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 and wait for result.
    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));
}