Inject dependent class in mapstruct mapper (kotlin) - 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

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

Can't use HiltAndroidRule in testing Room Dao

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.

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.

How to fix "[Dagger/MissingBinding]" in kotlin?

I'm trying to inject LatestChart in AppComponent and sloving issue with [Dagger/MissingBinding] LatestChart cannot be provided without an #Inject constructor or an #Provides-annotated method...
Thanks in advance for your help
LatestChart.kt
class LatestChart {
#Inject
lateinit var context: Context
#Inject
lateinit var formatter: YearValueFormatter
lateinit var chart: LineChart
init {
App.appComponent.inject(this)
}
fun initChart(chart: LineChart) {
this.chart = chart
***
}
fun addEntry(value: Float, date: Float) {
***
}
}
private fun createSet(): LineDataSet {
***
}
return set
}
}
And AppComponent.kt
#Component(modules = arrayOf(AppModule::class, RestModule::class, MvpModule::class, ChartModule::class))
#Singleton
interface AppComponent {
***
fun inject(chart: LatestChart)
}
Compilation error:
***AppComponent.java:8: error: [Dagger/MissingBinding] sn0w.test.crypto.chart.LatestChart cannot be provided without an #Inject constructor or an #Provides-annotated method. This type supports members injection but cannot be implicitly provided.
public abstract interface AppComponent {
^
A binding with matching key exists in component: sn0w.test.crypto.di.AppComponent
sn0w.test.crypto.chart.LatestChart is injected at
sn0w.test.crypto.activities.ChartActivity.latestChart
sn0w.test.crypto.activities.ChartActivity is injected at
sn0w.test.crypto.di.AppComponent.inject(sn0w.test.crypto.activities.ChartActivity)
The way you are currently trying to inject dependencies into the LatestChart is how you satisfy the dependencies in objects that won't be created by Dagger (e.g. activities). If you create the LatestChart object by yourself (just val latestCharts = LatestCharts()), you'll see that context and formatter are actually injected into this newly created instance.
However if you want Dagger to inject a LatestChart object into another (into ChartActivity I assume based on the compilation error), you have to let Dagger know how to create its instances. There are 2 ways to achieve that:
1. Annotate the LatestChart constructor with #Inject
class LatestChart #Inject constructor(
private val context: Context,
private val formatter: YearValueFormatter
) {
lateinit var chart: LineChart
fun initChart(chart: LineChart) { ... }
fun addEntry(value: Float, date: Float) { ... }
private fun createSet(): LineDataSet { ... }
}
2. Create a provider method in one of Dagger modules
class LatestChart(private val context: Context, private val formatter: YearValueFormatter) {
lateinit var chart: LineChart
fun initChart(chart: LineChart) { ... }
fun addEntry(value: Float, date: Float) { ... }
private fun createSet(): LineDataSet { ... }
}
#Module
class LatestChartModule {
#Module
companion object {
#JvmStatic
#Provides
fun provideLatestChart(context: Context, formatter: YearValueFormatter): LatestChart =
LatestChart(context, formatter)
}
}

Mocking ViewModel in Espresso

I'm writing Espresso UI test which mocks viewModel, referring GithubBrowserSample
what is the use of "TaskExecutorWithIdlingResourceRule", declaring Junit Rule will take care of IdlingResource?
Even after referring this "TaskExecutorWithIdlingResourceRule" class in my project whenever I build, compiler doesn't throw any error but when I run the test case it shows the Unresolved Error(s)
TaskExecutorWithIdlingResourceRule.kt
import androidx.arch.core.executor.testing.CountingTaskExecutorRule
import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.IdlingResource
import org.junit.runner.Description
import java.util.UUID
import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.TimeUnit
class TaskExecutorWithIdlingResourceRule : CountingTaskExecutorRule() {
// give it a unique id to workaround an espresso bug where you cannot register/unregister
// an idling resource w/ the same name.
private val id = UUID.randomUUID().toString()
private val idlingResource: IdlingResource = object : IdlingResource {
override fun getName(): String {
return "architecture components idling resource $id"
}
override fun isIdleNow(): Boolean {
return this#TaskExecutorWithIdlingResourceRule.isIdle
}
override fun registerIdleTransitionCallback(callback: IdlingResource.ResourceCallback) {
callbacks.add(callback)
}
}
private val callbacks = CopyOnWriteArrayList<IdlingResource.ResourceCallback>()
override fun starting(description: Description?) {
IdlingRegistry.getInstance().register(idlingResource)
super.starting(description)
}
override fun finished(description: Description?) {
drainTasks(10, TimeUnit.SECONDS)
callbacks.clear()
IdlingRegistry.getInstance().unregister(idlingResource)
super.finished(description)
}
override fun onIdle() {
super.onIdle()
for (callback in callbacks) {
callback.onTransitionToIdle()
}
}
}
Mocktest
#RunWith(AndroidJUnit4::class)
class MockTest {
#Rule
#JvmField
var activityRule = IntentsTestRule(SingleFragmentActivity::class.java, true, true)
#Rule
#JvmField
val executorRule = TaskExecutorWithIdlingResourceRule()
private lateinit var viewModel: SeriesFragmentViewModel
private val uiModelList = mutableListOf<SeriesBaseUIModel>()
private val seriesMutableLiveData = MutableLiveData<List<SeriesBaseUIModel>>()
private val seriesFragment = SeriesFragment()
#Before
fun init(){
viewModel = mock(SeriesFragmentViewModel::class.java)
`when`(viewModel.seriesLiveData).thenReturn(seriesMutableLiveData)
ViewModelUtil.createFor(viewModel)
activityRule.activity.setFragment(seriesFragment)
EspressoTestUtil.disableProgressBarAnimations(activityRule)
}
#Test
fun testLoading()
{
//Thread.sleep(3000)
uiModelList.add(ProgressUIModel())
seriesMutableLiveData.postValue(uiModelList.toList())
onView(withId(R.id.pod_series_recycler_view))
.check(selectedDescendantsMatch(withId(R.id.pod_adapter_series_header_title), isDisplayed()))
onView(withId(R.id.pod_series_recycler_view))
.check(selectedDescendantsMatch(withId(R.id.pod_adapter_series_header_title), withText(R.string.pod_series_header_title_text)))
onView(withId(R.id.pod_series_recycler_view))
.check(selectedDescendantsMatch(withId(R.id.pod_adapter_series_header_description), isDisplayed()))
onView(withId(R.id.pod_series_recycler_view))
.check(selectedDescendantsMatch(withId(R.id.pod_adapter_series_header_title), withText("Hello")))
Thread.sleep(5000)
}
}