Dagger2 Use a Provider<T> for #BindsInstance - kotlin

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!

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

Is it possible to verify at compile time whether the required function is called for the Factory Class in Kotlin?

class ModelFactory {
fun setA() : ModelFactory {
// blabla...
}
fun setB() : ModelFactory {
// blabla...
}
fun setC() : ModelFactory {
// blabla...
}
fun build() : Model {
// An error occurs if any of setA, setB, and setC is not called.
}
}
//example
fun successTest() {
ModelFactory().setA().setB().setC().build() // No error occurs at compile time
}
fun failTest() {
ModelFactory().setA().build() // An error occurs at compile time because setB and setC are not called.
}
It's awkward grammatically, but I think it's been expressed what I want.
I have already implemented an error-raising runtime for this requirement, but I want to check this at compile time.
If possible, I think I should use annotations. But is this really possible at compile time?
With Kotlin, I have been avoiding builder pattern, as we can always specify default values for non-mandatory fields.
If you still want to use a builder pattern, you can use Step builder pattern that expects all mandatory fields to be set before creating the object. Note that each setter method returns the reference of next setter interface. You can have multiple Step builders based on the combination of mandatory fields.
class Model(val a: String = "", val b: String = "", val c: String = "")
class StepBuilder {
companion object {
fun builder(): AStep = Steps()
}
interface AStep {
fun setA(a: String): BStep
}
interface BStep {
fun setB(b: String): CStep
}
interface CStep {
fun setC(c: String): BuildStep
}
interface BuildStep {
//fun setOptionalField(x: String): BuildStep
fun build(): Model
}
class Steps : AStep, BStep, CStep, BuildStep {
private lateinit var a: String
private lateinit var b: String
private lateinit var c: String
override fun setA(a: String): BStep {
this.a = a
return this
}
override fun setB(b: String): CStep {
this.b = b
return this
}
override fun setC(c: String): BuildStep {
this.c = c
return this
}
override fun build() = Model(a, b , c)
}
}
fun main() {
// cannot build until you call all three setters
val model = StepBuilder.builder().setA("A").setB("B").setC("C").build()
}

startKoin in KoinTest-class throws "A KoinContext is already started"

I'm using "withTestAppliction" in one of my tests to test if the route works. Before all Tests the DB-Table "cats" should have no entries. To get the DAO I need Koin in this Test but if conflicts with "withTestAppliction" where Koin will also be startet and throws A KoinContext is already started
[Update]
I know I could use something like handleRequest(HttpMethod.Delete, "/cats") but I don't want to expose this Rest-Interface. Not even for testing.
#ExperimentalCoroutinesApi
class CatsTest: KoinTest {
companion object {
#BeforeClass
#JvmStatic fun setup() {
// once per run
startKoin {
modules(appModule)
}
}
#AfterClass
#JvmStatic fun teardown() {
// clean up after this class, leave nothing dirty behind
stopKoin()
}
}
#Before
fun setupTest() = runBlockingTest {
val dao = inject<CatDAO>()
dao.value.deleteAll()
}
#After
fun cleanUp() {
}
#Test
fun testCreateCat() {
withTestApplication({ module(testing = true) }) {
val call = createCat(predictName("Pepples"), 22)
call.response.status().`should be`(HttpStatusCode.Created)
}
}
}
fun TestApplicationEngine.createCat(name: String, age: Int): TestApplicationCall {
return handleRequest(HttpMethod.Post, "/cats") {
addHeader(HttpHeaders.ContentType, ContentType.Application.FormUrlEncoded.toString())
setBody(listOf(
"name" to name,
"age" to age.toString()
).formUrlEncode())
}
}
After test (after withTestApplication()) call KoinContextHandler.get().stopKoin().
Example: https://github.com/comm1x/ktor-boot/blob/master/test/common/common.kt
It looks similar to the issue I faced. The problem was that the module() passed under the withTestApplication() was trying to create the Koin object again. I replaced the module() with specific modules that I had to load for the tests except for the Koin.
Refer - test sample and
application sample
Had the same problem executing multiple tests in a class. After removing the init/startKoin (since it's initialized in the Application when you test with the emulator).
I am not really sure if this is the correct approach, but it kind of works for me and my build server.
#ExperimentalCoroutinesApi
#RunWith(AndroidJUnit4ClassRunner::class) // or JUnit4..
class MyTest : KoinTest {
private val mockedAppModule: Module = module(override = true)
factory { myRepo }
}
#Before
fun setup() {
loadKoinModules(mockedAppModule)
}
#After
fun tearDown() {
unloadKoinModules(mockedAppModule)
}
#Test
fun testSubscriberRegistration() = runBlockingTest { // only needed if you are using supend functions
// test impl...
}
}

No definition found for qualifier: How to solve Kotlin reflection is not available when creating modules with Koin test?

I'm trying to create a simple white-box test with Koin. After setting qualifier to pass a mock as parameter to an instance (or supposedly what I want to do) I'm receiving an error which says:
org.koin.core.error.NoBeanDefFoundException: No definition found for qualifier='null' & class='class com.imagebucket.main.repository.GalleryRepositoryImpl (Kotlin reflection is not available)'
Test
class GalleryRepositoryTest: AutoCloseKoinTest() {
private val module = module {
named("repository").apply {
single<GalleryRepository>(this) { GalleryRepositoryImpl() }
single { GalleryViewModel(get(this.javaClass)) }
}
}
private val repository: GalleryRepositoryImpl by inject()
private val vm: GalleryViewModel by inject()
#Before
fun setup() {
startKoin { loadKoinModules(module) }
declareMock<GalleryRepositoryImpl>()
}
#Test
fun uploadImage_withEmptyUri_shouldDoNothing() {
vm.delete("")
verify(repository).delete("")
}
}
ViewModel
class GalleryViewModel(private val repository: GalleryRepository) : ViewModel() {
fun delete(name: String) {
repository.delete(name)
}
}
I also tried a similar approach with Robolectric runner, but after creating the module in Application file, my mock wouldn't be invoked using verify(repository).delete("").
How can I manage to solve this problem and move forward with this simple test?
As noticed by Arnaud at Koin's repository, issue #584, there should have a code replacement
from:
private val module = module {
single<GalleryRepository>(named("repository")) {
GalleryRepositoryImpl()
}
viewModel { GalleryViewModel(get()) }
}
private val repository: GalleryRepositoryImpl by inject()
private val vm: GalleryViewModel by inject(named("repository"))
to:
private val module = module {
single<GalleryRepository>(named("repository")) {
GalleryRepositoryImpl()
}
viewModel { GalleryViewModel(get(named("repository"))) }
}
private val repository: GalleryRepository by inject(named("repository"))
private val vm: GalleryViewModel by inject()

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