Можете ли вы использовать UnitTest для Android-рабочих, использующих внедрение конструктора Hilt?

Я исследую использование Hilt в моем текущем приложении для Android.

api 'androidx.hilt:hilt-work:1.0.0-alpha02'
implementation "com.google.dagger:hilt-android:2.30.1-alpha"
kapt 'com.google.dagger:hilt-android-compiler:2.30.1-alpha'
kapt 'androidx.hilt:hilt-compiler:1.0.0-alpha02'

api "androidx.work:work-runtime:2.4.0"
implementation "androidx.work:work-runtime-ktx:2.4.0"

testImplementation "androidx.work:work-testing:2.4.0"

testImplementation 'com.google.dagger:hilt-android-testing:2.30.1-alpha'
kaptTest 'com.google.dagger:hilt-android-compiler:2.30.1-alpha'

testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.7"
testImplementation "androidx.arch.core:core-testing:2.1.0"
testImplementation "junit:junit:4.13.1"
testImplementation "org.robolectric:robolectric:4.4"
testImplementation 'io.mockk:mockk:1.10.3'
testImplementation "androidx.test:core:1.3.0"
testImplementation "androidx.test.ext:junit:1.1.2"

Однако я не могу запустить свой androidx.work.CoroutineWorker UnitTest.

Они терпят неудачу с этим исключением:

java.lang.IllegalStateException: Could not create an instance of ListenableWorker com.my.manager.background.work.worker.MyWorker

    at androidx.work.testing.TestListenableWorkerBuilder.build(TestListenableWorkerBuilder.java:361)
    at com.my.manager.background.work.ApplicationFeaturesWorkerTest.testApplicationFeaturesWorker(MyWorkerTest.kt:13)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
    at org.junit.rules.TestWatcher$1.evaluate(TestWatcher.java:61)
    at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    at org.robolectric.RobolectricTestRunner$HelperTestRunner$1.evaluate(RobolectricTestRunner.java:575)
    at org.robolectric.internal.SandboxTestRunner$2.lambda$evaluate$0(SandboxTestRunner.java:263)
    at org.robolectric.internal.bytecode.Sandbox.lambda$runOnMainThread$0(Sandbox.java:89)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:834)

Конструктор My Worker выглядит так: -

class MyWorker @WorkerInject constructor(@Assisted context: Context, @Assisted params: WorkerParameters, private val service: MyApi) : BaseWorker(context, params) {
}

В документации Hilt указано: -

Сквозные тесты

Для интеграционных тестов Hilt внедряет зависимости, как в ваш производственный код. Тестирование с Hilt не требует обслуживания, поскольку Hilt автоматически генерирует новый набор компонентов для каждого теста.

Что я делаю неправильно?

Что мне нужно изменить, чтобы мои чистые модульные тесты могли создавать экземпляры Android CoroutineWorker, которые используют внедрение конструктора Hilt?

ОБНОВЛЕНИЕ

Мой UnitTest похож на это: -

class MyWorkerTest : BaseTest() {

    @Test
    fun testMyWorker() {
        val worker = TestListenableWorkerBuilder<MyWorker>(mContextMock).build()
        runBlocking {
            val result = worker.doWork()

            assert(result == ListenableWorker.Result.success())

        }
    }
}

Мой класс BaseTest: -

@RunWith(AndroidJUnit4::class)
@Config(manifest = Config.NONE, sdk = [O, O_MR1, P, Q])
abstract class BaseTest {

    lateinit var executor: Executor

    @get:Rule
    val instantTaskExecutorRule = InstantTaskExecutorRule()

    val mContextMock: Application = ApplicationProvider.getApplicationContext()

    @Before
    fun setup() {

        executor = Executors.newSingleThreadExecutor()
    }

    fun manufactureClient(): OkHttpClient {

        return OkHttpClient.Builder()
            .connectTimeout(OK_HTTP_CLIENT_TIMEOUT, TimeUnit.SECONDS)
            .readTimeout(OK_HTTP_CLIENT_TIMEOUT, TimeUnit.SECONDS)
            .writeTimeout(OK_HTTP_CLIENT_TIMEOUT, TimeUnit.SECONDS)
            .callTimeout(OK_HTTP_CLIENT_TIMEOUT, TimeUnit.SECONDS)
            .followSslRedirects(true)
            .retryOnConnectionFailure(true)
            .followRedirects(true)
            .addInterceptor(HttpLoggingInterceptor().apply {
                level = HttpLoggingInterceptor.Level.BASIC
            }).build()
    }

    @After
    fun tearDown() {

    }
}

person Hector    schedule 03.12.2020    source источник
comment
Не могли бы вы добавить пример теста, в котором вы пытаетесь использовать воркер?   -  person Andrew    schedule 06.12.2020
comment
Вероятно, это связано с тем, что WorkerFactory не вводится для контекста инструментария. Вам необходимо предоставить свой собственный бегун и убедиться, что рабочая фабрика предоставляется при инициализации WorkerManager.   -  person Nikola Despotoski    schedule 06.12.2020
comment
@NikolaDespotoski - это TestRunner (объявленный в gradle.build), используемый только для инструментальных тестов? Мне нужно использовать чистые JUnits   -  person Hector    schedule 07.12.2020
comment
Если вам нужно выполнить модульное тестирование, то, возможно, вам не нужно будет использовать TestListenableWorkerBuilder для создания экземпляра worker, но создайте его как любой другой класс.   -  person Nikola Despotoski    schedule 07.12.2020
comment
@NikolaDespotoski, спасибо за предложение, используя MockK, я смог самостоятельно создать экземпляр своего Worker и завершить модульный тест. Если вы оставите свой комментарий ОТВЕТ, то я могу наградить вас Баунти.   -  person Hector    schedule 10.12.2020


Ответы (1)


Если вам нужно провести модульное тестирование, то, возможно, вам не нужно будет использовать TestListenableWorkerBuilder для создания экземпляра Worker, но создайте его как любой другой класс.

class MyWorkerTest : BaseTest() {
    private lateinit var worker : MyWorker 
     
    @Before
    fun setupWorker(){ 
        worker = MyWorker(mockContext, mockOtherClass)
    } 
    @Test
    fun testMyWorker() = runBlocking {
       val result = worker.doWork()
       verify { mockOtherClass.someFunction() }
       //other assertions
      Unit 
    }
}

Модульное тестирование Worker должно проверять правильность вызовов и поведения внутри Worker doWork().

TestListenableWorkerBuilder используется в инструментальных испытаниях. Также рекомендуется избегать использования рукояти и кинжала для юнит-тестов.

person Nikola Despotoski    schedule 10.12.2020
comment
У меня проблема с созданием экземпляра моего рабочего класса, подобного этому, из-за наличия WorkerParameters в конструкторе по умолчанию CoroutineWorker. У кого-нибудь есть помощь в создании WorkerParameters экземпляра - person Jeff; 14.06.2021
comment
@Jeff WorkParameters - входные данные для класса. Создайте из него реальный объект и передайте его конструктору worker. - person Nikola Despotoski; 14.06.2021