Интеграционные тесты с WorkManager

WorkManager предоставляет артефакт work-testing , который помогает тестировать ваших работников.

Настраивать

Чтобы использовать артефакт work-testing , добавьте его как зависимость androidTestImplementation в build.gradle .

классный

dependencies {     def work_version = "2.5.0"      ...      // optional - Test helpers     androidTestImplementation "androidx.work:work-testing:$work_version" }

Котлин

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 , который упрощает написание тестов синхронным образом, без необходимости иметь дело с несколькими потоками, блокировками или защелками.

Вот пример того, как использовать все эти классы вместе.

Котлин

@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)     } }

Ява

@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 и просто копирует (эхо) свои входные данные в outputData .

Котлин

class EchoWorker(context: Context, parameters: WorkerParameters)    : Worker(context, parameters) {    override fun doWork(): Result {        return when(inputData.size()) {            0 -> Result.failure()            else -> Result.success(inputData)        }    } }

Ява

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

Базовые тесты

Ниже приведен тест Android Instrumentation, который тестирует EchoWorker . Главный вывод здесь заключается в том, что тестирование EchoWorker в тестовом режиме очень похоже на то, как вы будете использовать EchoWorker в реальном приложении.

Котлин

@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)) }

Ява

@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 не получает входных данных, ожидаемым Result является Result.failure() .

Котлин

@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)) }

Ява

@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 .

Тестирование начальных задержек

У рабочих могут быть первоначальные задержки. Чтобы протестировать EchoWorker с помощью initialDelay , вместо того, чтобы ждать initialDelay в вашем тесте, вы можете использовать TestDriver , чтобы пометить начальную задержку рабочего запроса как достигнутую с помощью setInitialDelayMet .

Котлин

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

Ява

@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 с ограничениями.

Котлин

@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     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)) }

Ява

@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 .

Котлин

@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)) }

Ява

@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)); }