Im investigating the use of Hilt in my current Android application.
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"
I cannot get my androidx.work.CoroutineWorker
UnitTest to run though.
They fail with this exception:-
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 constructor resembles this:-
class MyWorker @WorkerInject constructor(@Assisted context: Context, @Assisted params: WorkerParameters, private val service: MyApi) : BaseWorker(context, params) {
}
The Hilt documentation states:-
End-to-end tests
For integration tests, Hilt injects dependencies as it would in your production code. Testing with Hilt requires no maintenance because Hilt automatically generates a new set of components for each test.
What am I doing wrong?
What do I need to change to enable my pure UnitTests to be able to create instances of Android CoroutineWorker
that employ Hilt constructor injection?
UPDATE
My UnitTest resembles this:-
class MyWorkerTest : BaseTest() {
@Test
fun testMyWorker() {
val worker = TestListenableWorkerBuilder<MyWorker>(mContextMock).build()
runBlocking {
val result = worker.doWork()
assert(result == ListenableWorker.Result.success())
}
}
}
My BaseTest class:-
@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() {
}
}
If you need to unit test it, then you might not need to use TestListenableWorkerBuilder
to instantiate Worker
, but instantiate them as any other class.
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
}
}
Unit testing Worker
has to test correct calls and behavior inside the Worker
's doWork()
.
TestListenableWorkerBuilder
is used in instrumentation tests. Also hilt and dagger are suggested to be avoided for unit tests.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With