Kotlin - Don't run unit test until lateinit property is initialized - kotlin

I have a case where the unit of code I'm testing is running on a different thread and so the test executes and fails before the unit has finished executing:
class Tests {
private lateinit var result: String
#BeforeAll
fun setup() {
DataService().subscribe {
result = it
}
}
#Test
fun `get result from data service`() {
assert(result.contains("Hello"))
}
}
When the test runs, I get the following exception:
kotlin.UninitializedPropertyAccessException: lateinit property result has not been initialized
How can I ensure that the tests don't run before result has been initialized?

The test in the provided form won't work: #BeforeAll is allowed only on static methods, but then it couldn't access the result field. It can be solved by using #BeforeEach and #TestInstance(TestInstance.Lifecycle.PER_METHOD) [but see the UPDATE at the end of the post]
Regarding the result initialization itself: you can check if it has been initialized using this::result.isInitialized, e.g.:
#TestInstance(TestInstance.Lifecycle.PER_METHOD)
class AwaitTest {
private lateinit var result: String
#BeforeEach
fun setup() {
DataService().subscribe {
result = it
}
}
#Test
fun `get result from data service`() = runBlocking {
awaitInitialization()
assert(result.contains("Hello"))
}
suspend fun awaitInitialization() {
while(!this::result.isInitialized) {
delay(100)
}
}
}
Alternatively you could use awaitility:
#TestInstance(TestInstance.Lifecycle.PER_METHOD)
class AwaitTest {
private lateinit var result: String
#BeforeEach
fun setup() {
DataService().subscribe {
result = it
}
}
#Test
fun `get result from data service`() {
await until { this#AwaitTest::result.isInitialized }
assert(result.contains("Hello"))
}
}
Or even better, move await to the setup() method:
#TestInstance(TestInstance.Lifecycle.PER_METHOD)
class AwaitTest {
private lateinit var result: String
#BeforeEach
fun setup() {
DataService().subscribe {
result = it
}
await until { this#AwaitTest::result.isInitialized }
}
#Test
fun `get result from data service`() {
assert(result.contains("Hello"))
}
}
UPDATE:
Actually a #BeforeAll method doesn't need to be static if the test class is annotated with the#TestInstance(TestInstance.Lifecycle.PER_CLASS):
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
class AwaitTest {
private lateinit var result: String
#BeforeAll
fun setup() {
DataService().subscribe {
result = it
}
await until { this#AwaitTest::result.isInitialized }
}
#Test
fun `get result from data service`() {
assert(result.contains("Hello"))
}
}

Related

mocking a class that depends on firebase in a non-uI test for remote config

I have this class that exposes remote configs to others. I thought by creating a class, I would just mock it when testing others that use it but so far, firebase is blocking me. Not sure what I am doing wrong exactly.
class AppRemoteConfig #Inject constructor() {
private var remoteConfig: FirebaseRemoteConfig = Firebase.remoteConfig
private fun setListeningInterval(): Long {
if (BuildConfig.DEBUG){
return 0;
}
return 86400;
}
init {
val configSettings = remoteConfigSettings {
minimumFetchIntervalInSeconds = setListeningInterval()
}
remoteConfig.setConfigSettingsAsync(configSettings)
remoteConfig.setDefaultsAsync(R.xml.remote_config_defaults)
remoteConfig.fetchAndActivate()
.addOnCompleteListener(OnCompleteListener{
if (it.isSuccessful) {
remoteConfig.activate()
}
})
}
fun getString(key: String): String {
return this.remoteConfig.getString(key)
}
}
Now a class uses it this way:
class GetRData #Inject constructor(
private val _remoteConfig: AppRemoteConfig
) {
operator fun invoke(key): String {
try {
return _remoteConfig.getString(key)
} catch(ex: Exception){
return ""
}
return ""
}
}
Now I want to test GetRData class but I get the error: Default FirebaseApp is not initialized in this process null. Make sure to call FirebaseApp.initializeApp(Context) first.
here is what I have tried:
class GetRDataTest {
private var appRemoteConfig = mockk<AppRemoteConfig>(relaxed = true)
private lateinit var getRData : GetRData
#Before
fun setUp(){
getRData = GetRData(appRemoteConfig)
}
#Test
fun `Should get string value`() {
every { appRemoteConfig.getString("status") } returns "red"
val result = getRData.invoke("status")
verify { appRemoteConfig.getString("status") }
Truth.assertThat(result).isEqualTo("red")
}
}

Kafka listener not working for spring test

I have the following class to handle account update kafka events
#Component
class AccountUpdateQueueListener(
private val accountUpdateUseCase: AccountUpdateUseCase
) {
#KafkaListener(
topics = [Topics.ACCOUNT_UPDATES],
groupId = "api",
containerFactory = "accountUpdatesConsumeFactory",
)
fun onAccountUpdate(
#Payload request: AccountUpdateEventMessage,
#Header("_txId") txId: String,
acknowledgment: Acknowledgment
) {
MDC.put("account_id", request.account.id.toString())
MDC.put("_txId", txId)
try {
accountUpdateUseCase.update(AccountUpdateEventMessage.toCommand(request))
acknowledgment.acknowledge()
} catch (e: AccountNotFoundException) {
...
}
}
I am trying to test the accountUpdateUseCase.update function used in onAccountUpdate function is called with this test class
#SpringBootTest
#ActiveProfiles("testing")
#DirtiesContext
#EmbeddedKafka(partitions = 1, brokerProperties = ["listeners=PLAINTEXT://localhost:9092", "port=9092"])
class AccountUpdateQueueListenerTest {
#MockK
lateinit var accountUpdateUseCase: AccountUpdateUseCase
#InjectMockKs
lateinit var accountUpdateQueueListener: AccountUpdateQueueListener
#Autowired
lateinit var kafkaTemplate: KafkaTemplate<String, AccountUpdateEventMessage>
val accountUpdateEventMessage = AccountUpdateEventMessage(
...
)
#BeforeEach
fun init() {
MockKAnnotations.init(this)
}
#Test
fun `no errors and update is entered`() {
kafkaTemplate.send(Topics.ACCOUNT_UPDATES, "", accountUpdateEventMessage)
verify(exactly = 1) { accountUpdateUseCase.update(any()) }
}
}
The tests failed as accountUpdateUseCase.update. When running this with a debugger it is clear that onAccountUpdate is never entered. However, if the test case is rewritten as follows the debugger actually stopped in onAccountUpdate
#Test
fun `no errors and update is entered`() {
kafkaTemplate.send(Topics.ACCOUNT_UPDATES, "", accountUpdateEventMessage)
verify(exactly = 1) { accountUpdateQueueListener.onAccountUpdate(any(),any(),any()) }
}
My questions are why does the debugger stop in the second test case and how can I rewrite the first test to achieve my original goal?

How to mock extensions function in kotlin

I am using Mockk in my project. I am trying to mock my extension function but it cannot find my function. I tried some piece of code but it cannot find the extension function inside my test. Can someone guide me, How can I solve this issue. Thanks
ExploreConsultationsActivity.kt
class ExploreConsultationsActivity : AppCompactActvity () {
... // more function
internal fun setupExploreConsultationVisibility(hasFocus: Boolean) {
if (hasFocus) {
.....
} else if (viewModel.queryText.isEmpty()) {
binding.consultationViewSwitcher.displayConsultationViewSwitcherChild(0)
}
}
internal fun ViewSwitcher.displayConsultationViewSwitcherChild(childNumber: Int) {
visible()
displayedChild = childNumber
}
}
ExploreConsultationsActivityTest.kt
class ExploreConsultationsActivityTest {
#get:Rule
val testInstantTaskExecutorRule: TestRule = InstantTaskExecutorRule()
private val subject by lazy { spyk(ExploreConsultationsActivity()) }
#MockK private lateinit var mockConsultationViewSwitcher: ViewSwitcher
#Before
fun setUp() {
MockKAnnotations.init(this, relaxed = true)
setupMockView()
}
private fun setupMockView() {
mockkStatic(ExploreConsultationsLayoutBinding::class)
every { mockRootView.findViewById<ChipGroup>(R.id.exploreConsultationChips) } returns mockChipGroup
}
#Test
fun `setupExploreConsultationVisibility - `() {
// STUBBING
mockViewModel.queryText = ""
every { mockViewModel.topicSelected } returns ConsultationTopicsArea.ALL
with(mockConsultationViewSwitcher){
any<ViewSwitcher>().displayConsultationViewSwitcherChild(0)
}
// EXECUTION
subject.setupExploreConsultationVisibility(false)
// VERIFICATION
verify {
mockViewModel.filterBy(ConsultationTopicsArea.ALL)
}
}
I am getting this error

MockK - cannot mock same function twice

I am trying to test the getTopicNames function (below) in two scenarios: If it succeeds and if it does not succeed.
fun getTopicNames(): Either<Exception, Set<String>> =
try {
adminClient.listTopics()
.names()
.get()
.right()
} catch (exception: ExecutionException) {
exception.left()
}
This is the test class in which I am doing those two scenarios. If I run each test individually, they both suceed. If I run the entire class the second to execute fails because for some reason the previous mock on adminClient.listTopics() is being retained.
These are the versions for everything involved:
kotlin: 1.3.72
koin: 2.1.6
junit: 5.6.1
mockk: 1.10.0
class TopicOperationsTest {
#BeforeEach
fun start() {
val testModule = module(createdAtStart = true) {
single { mockk<AdminClient>() }
}
startKoin { modules(testModule) }
}
#AfterEach
fun stop() {
stopKoin()
}
#Test
fun `getTopicNames() returns a Right with the topics names`() {
val adminClient = get(AdminClient::class.java)
val listOfTopicsToReturn = mockk<ListTopicsResult>()
val expectedTopics = setOf("Topic1", "Topic2", "Topic3")
every { adminClient.listTopics() } returns listOfTopicsToReturn
every { listOfTopicsToReturn.names() } returns KafkaFuture.completedFuture(expectedTopics)
println("listOfTopicsToReturn.names(): " + listOfTopicsToReturn.names())
println("adminClient.listTopics(): " + adminClient.listTopics())
println("getTopicNames(): " + getTopicNames())
assertThat(getTopicNames().getOrElse { emptySet() }, `is`(expectedTopics))
}
#Test
fun `getTopicNames() returns a Left if failing to get topic names`() {
val adminClient = get(AdminClient::class.java)
every { adminClient.listTopics() } throws ExecutionException("Some Failure", Exception())
assertThat(getTopicNames(), IsInstanceOf(Either.Left::class.java))
}
}
This is the error I get, caused by the fact that the test that verifies the failure is the first to run:
java.lang.AssertionError:
Expected: is <[Topic1, Topic2, Topic3]>
but: was <[]>
Expected :is <[Topic1, Topic2, Topic3]>
Actual :<[]>
<Click to see difference>
Already tried clearAllMocks() on the BeforeEach method but it does not solve my problem as I just start getting:
io.mockk.MockKException: no answer found for: AdminClient(#1).listTopics()
I found a solution that makes everything work. It is a combination of:
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
Having the mock as a class object
MockKAnnotations.init(this) in the #BeforeEach method
clearMocks() specifying the actual mock to be cleared (should work for multiple mocks too, just separated by commas.
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TopicOperationsTest {
private var adminClientMock = mockk<AdminClient>()
#BeforeEach
fun start() {
MockKAnnotations.init(this)
val testModule = module(createdAtStart = true) {
single { adminClientMock }
}
startKoin { modules(testModule) }
}
#AfterEach
fun stop() {
clearMocks(adminClientMock)
stopKoin()
}
#Test
fun `getTopicNames() returns a Right with the topics names`() {
val adminClient = get(AdminClient::class.java)
val listOfTopicsToReturn = mockk<ListTopicsResult>()
val expectedTopics = setOf("Topic1", "Topic2", "Topic3")
every { adminClient.listTopics() } returns listOfTopicsToReturn
every { listOfTopicsToReturn.names() } returns KafkaFuture.completedFuture(expectedTopics)
assertThat(getTopicNames().getOrElse { emptySet() }, `is`(expectedTopics))
}
#Test
fun `getTopicNames() returns a Left if failing to get topic names`() {
val adminClient = get(AdminClient::class.java)
every { adminClient.listTopics() } throws ExecutionException("Some Failure", Exception())
assertThat(getTopicNames(), IsInstanceOf(Either.Left::class.java))
}
}

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