androidkotlintestingcoroutineworker

Test a Worker with Hilt Dependency Injection


I'm trying to test my worker class, but when I attempt to create the worker instance, it gives me this error

java.lang.IllegalStateException: Could not create an instance of ListenableWorker

This is the code of my Worker class

@HiltWorker
class MyWorker @AssistedInject constructor(
    @Assisted private val context: Context,
    @Assisted parameters: WorkerParameters,
    private val repository: IMyRepository // this is an interface 
) : CoroutineWorker(context, parameters) {
    ////
}

I'm trying to test my worker with this code:

@HiltAndroidTest
class WorkerTest {

    @get:Rule
    val hiltRule = HiltAndroidRule(this)
    private lateinit var appContext: Context

    @Before
    fun setUp() {
        appContext = ApplicationProvider.getApplicationContext()
        hiltRule.inject()
    }

    @Test
    fun testMyWorker() {
        val worker = TestListenableWorkerBuilder<DecryptionWorker>(
            context = appContext,
        ).build() // Error on this line
    }
}

And this code is my test repository implementation:

@Singleton
class TestMyRepositoryImpl @Inject constructor(): IMyRepository {
    ////
}

I'm not sure, but I think this is the correct way to bind my test repository and replace my actual CoreModule

@Module
@TestInstallIn(
    components = [SingletonComponent::class],
    replaces = [CoreModule::class]
)
abstract class TestCoreModule {
    @Binds
    abstract fun provideMyRepository(myRepositoryImpl: TestMyRepositoryImpl): IMyRepository
}

Solution

  • I learned from this article that you can create a worker factory and use it to create an instance of your worker. With that said, here's my current implementation.

    class TestWorkerFactory(
        private val myRepository: IMyRepository
    ) : WorkerFactory() {
        override fun createWorker(
            appContext: Context,
            workerClassName: String,
            workerParameters: WorkerParameters
        ): ListenableWorker? {
            return when (workerClassName) {
                MyWorker::class.java.name -> MyWorker(
                    appContext,
                    workerParameters,
                    myRepository
                )
    
                else -> null
            }
        }
    }
    

    Since I'm still using the TestCoreModule, this is my current test code:

    @HiltAndroidTest
    class WorkerTest {
    
        @get:Rule
        val hiltRule = HiltAndroidRule(this)
    
        private lateinit var appContext: Context
    
        @Inject
        lateinit var myRepository: IMyRepository
    
        @Before
        fun setUp() {
            appContext = ApplicationProvider.getApplicationContext()
            hiltRule.inject()
        }
    
        @Test
        fun testMyWorker() {
            val worker = TestListenableWorkerBuilder<MyWorker>(
                context = appContext
            ).setWorkerFactory(
                TestWorkerFactory(myRepository)
            ).build()
    
            // The rest of my test code
        }
    }
    

    Note: I'm open to feedback and learning from others, so if you happen to notice any flaws in my current implementation, please feel free to share your insights. I'm continuously learning.