Can't use HiltAndroidRule in testing Room Dao - kotlin

I want use Hilt for Room Dao testing.
I learned from this project
https://github.com/philipplackner/ShoppingListTestingYT/tree/TestingWithHilt
My testing failed and show that:
java.lang.IllegalStateException: Hilt test, com.example.allinone.page2.testAndHilt.data.local.TestItemDaoTest, cannot use a #HiltAndroidApp application but found com.example.allinone.main.MainApplication. To fix, configure the test to use HiltTestApplication or a custom Hilt test application generated with #CustomTestApplication.
at dagger.hilt.internal.Preconditions.checkState(Preconditions.java:83)
at dagger.hilt.android.internal.testing.MarkThatRulesRanRule.(MarkThatRulesRanRule.java:63)
at dagger.hilt.android.testing.HiltAndroidRule.(HiltAndroidRule.java:36)
at com.example.allinone.page2.testAndHilt.data.local.TestItemDaoTest.(TestItemDaoTest.kt:27)
in AppModule:
#Module
#InstallIn(SingletonComponent::class)
object AppModuleTest {
#Provides
#Named("test_db")
fun provideInMemoryDB(#ApplicationContext context: Context) =
Room.inMemoryDatabaseBuilder(context, TestItemDataBase::class.java)
.allowMainThreadQueries()
.build()
}
in DaoTest:
#SmallTest
#HiltAndroidTest
//#RunWith(AndroidJUnit4::class)
class TestItemDaoTest {
#get:Rule
var hiltRule = HiltAndroidRule(this)
#get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
#Inject
#Named("test_db")
lateinit var database: TestItemDataBase
lateinit var dao: TestItemDao
#Before
fun setup() {
hiltRule.inject()
dao = database.testItemDao()
}
#After
fun teardown() {
database.close()
}
#Test
fun test() {
assertEquals(1, 1)
}
}
Where is the problem?
I didn't use MainApplication.
And I want use
hiltRule.inject()
so I don't know how to rewrite.

It is an IDE bug.
Just invalidae cachs and restart.

Related

Mockk #OverrideMockKs not working with Kotest

I've using Kotest recently and I hadn't had any issues, but recently I was trying some annotations for dependency injection so to simplify the problem I created some basic classes with some methods that just print some messages, just for the sake of learning how to use Kotest and Mockk, but during the testing, I ran with the exception that the variable hasn't been initialized when trying to run the test.
These are my classes
class DefaultClass : AbstractClass() {
private val anotherClass: AnotherClass = AnotherClass()
fun testMethod(value: String): String {
val normalizeValue = value.trim().lowercase().replace(Regex("[^ A-Za-z\\d]*"), "")
return runBlocking {
anotherClass.someOtherMethod()
callsProtectedMethod(normalizeValue)
}
}
private suspend fun callsProtectedMethod(value: String) = coroutineScope {
println("Original method")
returnDefaultString(value)
}
}
AnotherClass
class AnotherClass {
fun someOtherMethod(): Unit {
println("SomeOtherMethod original")
}
}
Test
class DefaultClassTest : FunSpec({
context("Testing DefaultClass") {
#MockK
lateinit var anotherClass: AnotherClass
#OverrideMockKs
lateinit var defaultClass: DefaultClass
beforeContainer {
MockKAnnotations.init(this)
}
test("testing mocks") {
defaultClass.testMethod("some method")
}
}
I've changed the initialization to beforeTest, taken it out of the context, and also use beforeContainer, beforeTest, beforeSpec, but none of these work... every time I still get lateinit property defaultClass has not been initialized
So, I recreated the same test using JUnit and I don't have this issue.
class DefaultClassJUnitTest {
companion object {
#MockK
lateinit var anotherClass: AnotherClass
#OverrideMockKs
lateinit var defaultClass: DefaultClass
#BeforeAll
#JvmStatic
fun setup() {
MockKAnnotations.init(this)
}
}
#Test
fun `Testing with JUnit`() {
every { anotherClass.someOtherMethod() } answers {
println("Mocking another class")
}
val value = defaultClass.testMethod("some method")
}
}
So I'm pretty sure that I'm doing something wrong when using Kotest. I hope anyone might help me, thanks...
I think MockK is probably not looking for variables defined within function scopes. If you want to use the annotations, you likely have to move them to the companion object, like this:
class DefaultClassTest : FunSpec({
context("Testing DefaultClass") {
beforeContainer {
MockKAnnotations.init(this)
}
test("testing mocks") {
defaultClass.testMethod("some method")
}
}
}) {
companion object {
#MockK
lateinit var anotherClass: AnotherClass
#OverrideMockKs
lateinit var defaultClass: DefaultClass
}
}

Inject dependent class in mapstruct mapper (kotlin)

How can I inject ReadProcessor class while writing test case for BookMapper
BookMapper.kt
#Mapper(componentModel = "spring")
abstract class BookMapper {
#Autowired
lateinit var processor: ReadProcessor
#Mappings(
Mapping(target = "name", expression = "java(this.getFullName(entity.getId()))")
)
abstract fun entityToReport(entity: BookEntity): BookReport
fun getFullName(id: String): String {
return processor.onRead(id)
}
}
ReadProcessor.kt
class ReadProcessor() {
fun onRead(id: String): String = "test"
}
BookMapperTest.kt
#ExtendWith(MockKExtension::class)
class BookMapperTest {
#MockK
private lateinit var processor: ReadProcessor
private lateinit var mapper: BookMapper
#BeforeEach
fun setUp() {
mapper = Mappers.getMapper(BookMapper::class.java)
}
#Test
fun `should map entity to report object`() {
// Given
val entity = BookEntity(id="123")
every { processor.onRead("256") } returns "test"
// When
val report = mapper.entityToReport(entity)
// Then
assertThat(report.name).isEqualTo("test")
}
}
when I run above test case it is throwing below error
kotlin.UninitializedPropertyAccessException: lateinit property processor has not been initialized
at BookMapper.getFullName(BookMapper.kt:24)
Kindly let me know how to write test case for this scenario.
Kotlin version: 1.3.70
java version: 1.8
mapstruct version: 1.4.2.Final
Junit-jupiter version : 5.6.2

Dagger2 Use a Provider<T> for #BindsInstance

Is it possible to bind a Provider<T> instead of just an instance T?
For example, I have this:
#Component()
interface Module {
#Component.Builder
interface Builder {
#BindsInstance
fun config(config: Config): Builder
}
}
I would like to do something like this (but it doesn't work):
#Component()
interface Module {
#Component.Builder
interface Builder {
#BindsInstance
fun config(config: Provider<Config>): Builder
}
}
What am I trying to do?
NOTE: The Config class is immutable
I would like to be able to change the config value for different tests, e.g.
class Test {
val configProvider: CustomProvider<Config>
val classUnderTest: Provider<T>
#Before
fun setUp() {
val module = DaggerModule()
.setConfig(configProvider)
.build()
// Do other setup with module
classUnderTest = module.getTestClassProvider()
}
#Test
fun test() {
configProvider.setValue(CONFIG_1)
classUnderTest.get().doSomething();
}
#Test
fun test2() {
configProvider.setValue(CONFIG_2)
classUnderTest.get().doSomething();
}
}
I'm trying to avoid having to finish building the module in each test case and then calling another function to handle finishing the setup, e.g.
#Before
fun setUp() {
moduleBuilder = DaggerModule()
}
#Test
fun test() {
val module = moduleBuilder.setConfig(CONFIG_1).build()
finishSetup(module)
// Do test
}
Thanks for your help!

Issue compiling with dependency injection

Error with Kotlin conversion from java, this project is testing Github Api and displaying data in recyclerview.I get the a compile error which i cannot workout for Dagger2, it worked in Java but when converting to Kotlin i get a compile error at runtime.
It seems to be with injecting an api method into the view model
i have tried following the error then cleaning and rebuilding the app
I have also tried invalidating caches and restarting but seems there is an error with the conversion into Kotlin from Java. Any help would be appreciated.
Here is my class:
class RepoRepository {
private lateinit var repoService: GithubRepos
#Inject
fun RepoRepository(repoService: GithubRepos) {
this.repoService = repoService
}
fun getRepositories(): Single<List<Repo>> {
return repoService.getRepos()
}
fun getSingleRepo(owner: String, name: String): Single<Repo> {
return repoService.getSingleRepo(owner, name)
}
}
My component class:
#Singleton
#Component(modules = arrayOf(NetworkModule::class))
interface AppComponent {
/**
* inject required dependencies into MainActivityListViewModel
*/
fun inject(mainActivityListViewModel: MainActivityListViewModel)
#Component.Builder
interface Builder {
fun build(): AppComponent
fun networkModule(networkModule: NetworkModule): Builder
}
}
And my ViewModel:
class MainActivityListViewModel : BaseViewModel() {
private lateinit var repoRepository: RepoRepository
private var disposable: CompositeDisposable? = null
private val repos = MutableLiveData<List<Repo>>()
private val repoLoadError = MutableLiveData<Boolean>()
private val loading = MutableLiveData<Boolean>()
#Inject
fun ListViewModel(repoRepository: RepoRepository) {
this.repoRepository = repoRepository
disposable = CompositeDisposable()
fetchRepos()
}
fun getRepos(): LiveData<List<Repo>> {
return repos
}
fun getError(): LiveData<Boolean> {
return repoLoadError
}
fun getLoading(): LiveData<Boolean> {
return loading
}
private fun fetchRepos() {
loading.value = true
disposable?.add(repoRepository.getRepositories()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object :
DisposableSingleObserver<List<Repo>>() {
override fun onSuccess(value: List<Repo>) {
repoLoadError.value = false
repos.value = value
loading.value = false
}
override fun onError(e: Throwable) {
repoLoadError.value = true
loading.value = false
}
}))
}
override fun onCleared() {
super.onCleared()
if (disposable != null) {
disposable!!.clear()
disposable = null
}
}
}
this is error i am getting:
[Dagger/MissingBinding] repos.network.RepoRepository cannot be
provided without an #Inject constructor or an #Provides-annotated
method. This type supports members injection but cannot be
implicitly provided.
public abstract repos.network.RepoRepository
repoRepository();
^
repos.network.RepoRepository is provided at
components.AppComponent.repoRepository() e: repos/di/components/AppComponent.java:19: error:
[Dagger/MissingBinding] repos.network.RepoRepository cannot be
provided without an #Inject constructor or an #Provides- annotated
method. This type supports members injection but cannot be
implicitly provided.
^
repos.network.RepoRepository is injected at
repos.viewmodels.MainActivityListViewModel.ListViewModel(repoRepository)
repos.viewmodels.MainActivityListViewModel is injected at
repos.di.components.AppComponent.inject(repos.viewmodels.MainActivityListViewModel)
Your error clearly says:
[Dagger/MissingBinding] repos.network.RepoRepository cannot be provided without an #Inject constructor or an #Provides-annotated method.
You didn't define constructor for your RepoRepository class.
It should look something like this:
class RepoRepository #Inject constructor(private val repoService: GithubRepos) {//the rest of your code here}
This goes for your viewmodel class as well.
If you are using android ViewModel architecture component i suggest you read this article which explains how to use it with Dagger2.
Hope this helps.

lateinit property has not been initialized with dagger and coroutine

I'm trying MVVM, Dagger2, Retrofit and Coroutine. Now I have the problem that I can successfully inject a ProfileService into my activity, but not into my repository. I get a profileService lateinit property has not been initialized
//MainActivity
#Inject
lateinit var profileService: ProfileService //only for testing
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val getTheExpectedResult = profileService.hasValidName("asdffff") //true
profileViewModel.createProfile("ku") //throw the not initialized
}
The viewmodel calls the repository.
// profile repository
private fun getProfileRepository(userId: String = "", apiKey: String = ""): ProfileRepository {
return ProfileRepository(ApiFactory.getApi(userId, apiKey))
}
fun createProfile(name: String) {
scope.launch {
try {
val profile = getProfileRepository().createProfile(name)
profileLiveData.postValue(profile)
}
//...
In the repository I inject the profileService
class ProfileRepository(private val api: NIPApiInterface) {
#Inject
lateinit var profileService: ProfileService
suspend fun createProfile(name: String): ProfileResponse? {
if (!profileService.hasValidName(name)) { //throw the not initialized
//...
My unspectacular ProfileService
class ProfileService #Inject constructor() {
fun hasValidName(name: String): Boolean {
return name.length > 3
}
}
So that I don't post too much code, the following info. In my Application I seem to initialize everything correctly, because the Activity can access the ProfileService. Here are my Dagger configurations:
//AppComponent
#Singleton
#Component(
modules = [
AndroidSupportInjectionModule::class, AppModule::class, ActivityBuilder::class
]
)
interface AppComponent : AndroidInjector<NIPApplication> {
#Component.Factory
interface Factory {
fun create(#BindsInstance application: NIPApplication): AppComponent
}
}
The AppModule
#Module
class AppModule {
#Provides
#Singleton
fun provideContext(app: NIPApplication): Context = app
}
If you need more code, please send a comment.
Dagger won't inject dependencies unless it creates an instance of a class that needs injection or is explicitly asked to. Since the ProfileRepository object is created by you, it won't get its dependencies injected by Dagger.
The best way to solve this is to let Dagger create ProfileRepository objects by #Inject-annotated constructor (or using #Provides-annotated method in a Dagger module (provider), however this seems quite redundant in this particular case):
class ProfileRepository #Inject constructor(
private val api: NIPApiInterface,
private val profileService
)
Note that now Dagger will want to inject an NIPApiInterface object as well, so you have to create a provider for that or remove it from the constructor and pass it in some other way.