Testing custom exceptions in Kotlin with JUnit - kotlin

I have the following class
data class CarDefects(
private val _carModel: CarModel,
private val _affectedYearsOfIssue: List<Year>,
private val _defectCode: String
) {
init {
validateDefectCode(_defectCode)
}
}
Validating function
fun validateDefectCode(defectCode: String) {
val pattern = Pattern.compile("^[a-zA-Z0-9-]*\$")
val m = pattern.matcher(defectCode)
if (defectCode.length !in 4..4) {
throw InvalidDefectCodeException(defectCode, "Defect code must be 4 characters long")
}
if (!m.matches()) {
throw InvalidDefectCodeException(defectCode, "Defect code can only contain alphanumeric characters")
}
}
And the exception class:
class InvalidDefectCodeException(_defectCode:String, message:String):
IllegalArgumentException("Invalid defect code $_defectCode. $message") {
}
I'm trying to test the validating function with JUnit
import car.exceptions.InvalidDefectCodeException
import car.validators.carDefectsValidators.validateDefectCode
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import java.time.Year
import kotlin.test.assertFailsWith
internal class CarDefectsTest {
val carModel = CarModel(Brand.BMW, "X5", 199219)
val carModel2 = CarModel(Brand.AUDI, "X434", 199219)
val defect = CarDefects(carModel, listOf(Year.of(2020), Year.of(2021)), "SE2#")
val defect2 = CarDefects(carModel2, listOf(Year.of(2020), Year.of(2021)), "122F4")
#Test
fun testDefectCodeExceptions() {
val exception = Assertions.assertThrows(InvalidDefectCodeException::class.java) {
validateDefectCode(defect.getDefectCode())
}
}
#Test
fun testDefectCodeExceptions2() {
assertFailsWith<InvalidDefectCodeException> {
validateDefectCode(defect2.getDefectCode())
}
}
}
Both tests fail, however expected exceptions are still thrown, from what i understand shouldn't both tests pass?
I've already seen the following post: Test expected exceptions in Kotlin

Inside class CarDefects, you're having this init block:
init {
validateDefectCode(_defectCode)
}
Hence, the exception will be thrown during construction.
Let's test the constructor instead with a stripped down CarDefects class. The following tests are passing on my computer.
import car.exceptions.InvalidDefectCodeException
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import kotlin.test.assertFailsWith
data class CarDefects(
private val defectCode: String
) {
init {
validateDefectCode(defectCode)
}
}
internal class CarDefectsTest {
#Test
fun testDefectCodeExceptions() {
Assertions.assertThrows(InvalidDefectCodeException::class.java) {
CarDefects(defectCode = "SE2#")
}
}
#Test
fun testDefectCodeExceptions2() {
assertFailsWith<InvalidDefectCodeException> {
CarDefects(defectCode = "122F4")
}
}
}

Related

TransactionalEventListener not invoked in #MicronautTest

Problem description
While system end-to-end tests are invoking methods annotated with #TransactionalEventListener, I'm not able to invoke the same methods in narrower tests annotated with #MicronautTest.
I've tested numerous variants with both injected EntityManager and SessionFactory. #MicronautTest(transactional = false) is also tested. Calling JPA-method inside TestSvcWithTxMethod#someMethod is also tested with same result. I've also tried tests without mocking TestAppEventListener.
The below test/code yields
Verification failed: call 1 of 1:
TestAppEventListener(#1).beforeCommit(any())) was not called.
java.lang.AssertionError: Verification failed: call 1 of 1:
TestAppEventListener(#1).beforeCommit(any())) was not called.
Calls to same mock: 1) TestAppEventListener(#1).hashCode()
Environment: Micronaut 3.7.5, Micronaut Data 3.9.3
Minimal reproducible code
Test is failing as well with transactional = false
import io.kotest.core.spec.style.BehaviorSpec
import io.micronaut.test.annotation.MockBean
import io.micronaut.test.extensions.kotest5.MicronautKotest5Extension.getMock
import io.micronaut.test.extensions.kotest5.annotation.MicronautTest
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import no.mycompany.myapp.eventstore.services.appeventpublisher.testinfra.DefaultTestAppEventListener
import no.mycompany.myapp.eventstore.services.appeventpublisher.testinfra.TestAppEventListener
import no.mycompany.myapp.eventstore.services.appeventpublisher.testinfra.TestSvcWrapper
#MicronautTest
class AppEventWithBeforeCommitListenerMockTest(
testSvcWrapper: TestSvcWrapper,
testAppEventListener: TestAppEventListener
) : BehaviorSpec({
given("context with app event listener") {
`when`("calling someMethod") {
val mockBeforeCommitTestListener = getMock(testAppEventListener)
every { mockBeforeCommitTestListener.beforeCommit(any()) } answers {}
every { mockBeforeCommitTestListener.afterRollback(any()) } answers {}
testSvcWrapper.someMethod(message = "call #1")
verify { mockBeforeCommitTestListener.beforeCommit(any()) }
}
}
}) {
#MockBean(DefaultTestAppEventListener::class)
fun mockTestAppEventListener(): TestAppEventListener = mockk()
}
TestSvcWrapper
import jakarta.inject.Singleton
#Singleton
class TestSvcWrapper(
private val testSvcWithTxMethod: TestSvcWithTxMethod
) {
fun someMethod(message: String) {
testSvcWithTxMethod.someMethod(message)
}
}
TestSvcWithTxMethod
import io.micronaut.context.event.ApplicationEventPublisher
import jakarta.inject.Singleton
import javax.transaction.Transactional
#Singleton
open class TestSvcWithTxMethod(
private val eventPublisher: ApplicationEventPublisher<TestEvent>
) {
#Transactional(Transactional.TxType.REQUIRES_NEW)
open fun someMethod(message: String) {
eventPublisher.publishEvent(TestEvent(message))
}
}
TestEvent
import io.micronaut.core.annotation.Introspected
#Introspected
data class TestEvent(val message: String)
TestAppEventListener
interface TestAppEventListener {
fun beforeCommit(event: TestEvent)
fun afterRollback(event: TestEvent)
}
DefaultTestAppEventListener
import io.micronaut.transaction.annotation.TransactionalEventListener
import jakarta.inject.Singleton
import java.util.concurrent.atomic.AtomicInteger
#Singleton
open class DefaultTestAppEventListener : TestAppEventListener {
val receiveCount = AtomicInteger()
#TransactionalEventListener(TransactionalEventListener.TransactionPhase.BEFORE_COMMIT)
override fun beforeCommit(event: TestEvent) {
receiveCount.getAndIncrement()
}
#TransactionalEventListener(TransactionalEventListener.TransactionPhase.AFTER_ROLLBACK)
override fun afterRollback(event: TestEvent) {
receiveCount.getAndIncrement()
}
}
The answer was found in the micronaut-test repo. Key is to inject SynchronousTransactionManager<Any>, create and then commit/rollback transaction.
I was not able to make mock-test from question pass, most likely because of the annotations, but the following tests are working. I made some modifications to the types in question, hence I added code for the new implementations below.
import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.shouldBe
import io.micronaut.test.extensions.kotest5.annotation.MicronautTest
import io.micronaut.transaction.SynchronousTransactionManager
import io.micronaut.transaction.support.DefaultTransactionDefinition
import no.mycompany.myapp.eventstore.services.appeventpublisher.testinfra.TestAppEventListener
import no.mycompany.myapp.eventstore.services.appeventpublisher.testinfra.TestSvcWithTxMethod
#MicronautTest(transactional = false)
class AppEventWithBeforeCommitListenerTest(
testSvcWithTxMethod: TestSvcWithTxMethod,
testAppEventListener: TestAppEventListener,
transactionManager: SynchronousTransactionManager<Any>
) : BehaviorSpec({
given("context with app event listener") {
`when`("calling someMethod with commit") {
val tx = transactionManager.getTransaction(DefaultTransactionDefinition())
testSvcWithTxMethod.someMethod(message = "call #1")
transactionManager.commit(tx)
then("TestAppEventListener should have received message") {
testAppEventListener.beforeCommitReceiveCount.get() shouldBe 1
}
}
`when`("calling someMethod with rollback") {
val tx = transactionManager.getTransaction(DefaultTransactionDefinition())
testSvcWithTxMethod.someMethod(message = "call #2")
transactionManager.rollback(tx)
then("TestAppEventListener should have received message") {
testAppEventListener.afterRollbackReceiveCount.get() shouldBe 1
}
}
}
})
TestSvcWithTxMethod
import io.micronaut.context.event.ApplicationEventPublisher
import jakarta.inject.Singleton
import javax.transaction.Transactional
#Singleton
open class TestSvcWithTxMethod(
private val eventPublisher: ApplicationEventPublisher<TestEvent>
) {
#Transactional
open fun someMethod(message: String) {
eventPublisher.publishEvent(TestEvent(message))
}
}
TestAppEventListener
import io.micronaut.transaction.annotation.TransactionalEventListener
import jakarta.inject.Singleton
import java.util.concurrent.atomic.AtomicInteger
#Singleton
open class TestAppEventListener {
val beforeCommitReceiveCount = AtomicInteger()
val afterRollbackReceiveCount = AtomicInteger()
#TransactionalEventListener(TransactionalEventListener.TransactionPhase.BEFORE_COMMIT)
open fun beforeCommit(event: TestEvent) {
beforeCommitReceiveCount.getAndIncrement()
}
#TransactionalEventListener(TransactionalEventListener.TransactionPhase.AFTER_ROLLBACK)
open fun afterRollback(event: TestEvent) {
afterRollbackReceiveCount.getAndIncrement()
}
}
TestEvent (unchanged)
import io.micronaut.core.annotation.Introspected
#Introspected
data class TestEvent(val message: String)

Android: unit testing of LiveData and Flow

I'm trying to write unit testing for my ViewModel but I don't know how to deal with LiveData functions.
Specifically I'm not able to validate all the values that receive the LiveData Observer.
Regarding I have a Flow Use case that emit values and then is pased as a LiveData, what is the best approach to test operation function?
In the code below you can find that I'm only able to read the value "endLoading", but I want to check all the values: "startLoading", "Hello Dummy $input", "endLoading"
MainViewModel.kt
class MainViewModel(val useCase: DummyUseCase = DummyUseCase()): ViewModel() {
fun operation(value: Int): LiveData<String> = useCase.invoke(value)
.transform { response ->
emit(response)
}.onStart {
emit("startLoading")
}.catch {
emit("ERROR")
}.onCompletion {
emit("endLoading")
}.asLiveData(viewModelScope.coroutineContext)
}
MainViewModelTest.kt
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.lifecycle.Observer
import io.mockk.MockKAnnotations
import io.mockk.coEvery
import io.mockk.impl.annotations.MockK
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Assert.assertNotNull
import org.junit.Before
import org.junit.Rule
import org.junit.Test
#ExperimentalCoroutinesApi
class MainViewModelTest {
//region Setup
#get:Rule
val rule = InstantTaskExecutorRule()
private val testDispatcher = TestCoroutineDispatcher()
#MockK private lateinit var stateObserver: Observer<String>
#MockK private lateinit var useCase: DummyUseCase
private lateinit var viewModel: MainViewModel
#Before
fun setup() {
MockKAnnotations.init(this, relaxUnitFun = true)
Dispatchers.setMain(testDispatcher)
viewModel = MainViewModel(useCase)
}
#After
fun teardown() {
Dispatchers.resetMain()
testDispatcher.cleanupTestCoroutines()
}
//endregion
#Test // AAA testing
fun `when my flow succeeds, return a state String`() {
runBlocking {
//Arrange
val input = 10
coEvery { useCase.invoke(input) }.returns(flow {
emit("Hello Dummy $input")
})
//Act
val actual = viewModel.operation(input).apply {
observeForever(stateObserver)
}
//Assert
// I want to assert here every value received in the observer of the "actual" LiveData
// How? :(
assertNotNull(actual.value) // is always "endLoading"
}
}
}
You can test the LiveData using a custom Observer<T> implementation. Create an observer which records all emmited values and lets you assert against the history.
The Observer which records the values may look like this:
class TestableObserver<T> : Observer<T> {
private val history: MutableList<T> = mutableListOf()
override fun onChanged(value: T) {
history.add(value)
}
fun assertAllEmitted(values: List<T>) {
assertEquals(values.count(), history.count())
history.forEachIndexed { index, t ->
assertEquals(values[index], t)
}
}
}
You can assert if all given values were emitted by the LiveData using the assertAllEmitted(...) function.
The test function will use an instance of the TestableObserver class instead of a mocked one:
#Test // AAA testing
fun `when my flow succeeds, return a state String`() {
runBlocking {
//Arrange
val stateObserver = TestableObserver<String>()
val input = 10
coEvery { useCase.invoke(input) }.returns(flow {
emit("Hello Dummy $input")
})
//Act
val actual = viewModel.operation(input).apply {
observeForever(stateObserver)
}
//Assert
stateObserver.assertAllEmitted(
listOf(
"startLoading",
"Hello Dummy 10",
"endLoading"
)
)
}
}
Asserting the history of LiveData may be possible using mocking frameworks and assertion frameworks however, I think implementing this testable observer is more readable.

kotlin flow is not emitting values from different function

I am trying to implement kotlin stateflow, but not able to know the reason why it is not working.
Current output:
verificatio 34567
Expected Output:
verificatio 34567
verificatio failed
package stateflowDuplicate
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.runBlocking
fun main() = runBlocking {
val firebasePhoneVerificationListener = FirebaseOTPVerificationOperation1()
val oTPVerificationViewModal = OTPVerificationViewModal1(firebasePhoneVerificationListener)
oTPVerificationViewModal.fail()
}
class OTPVerificationViewModal1(private val firebasePhoneVerificationListener: FirebaseOTPVerificationOperation1) {
init {
startPhoneNumberVerification()
setUpListener()
}
suspend fun fail(){
firebasePhoneVerificationListener.fail()
}
private fun startPhoneNumberVerification() {
firebasePhoneVerificationListener.initiatePhoneVerification("34567")
}
private fun setUpListener() {
runBlocking {
firebasePhoneVerificationListener.phoneVerificationFailed.collect {
println("verificatio $it")
}
}
}
}
Second class
package stateflowDuplicate
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.runBlocking
class FirebaseOTPVerificationOperation1 {
private val _phoneVerificationFailed = MutableStateFlow<String?>(null)
val phoneVerificationFailed: StateFlow<String?>
get() = _phoneVerificationFailed
fun initiatePhoneVerification(phoneNumber: String) {
_phoneVerificationFailed.value = phoneNumber
}
suspend fun fail() {
delay(200L)
_phoneVerificationFailed.value = "failed"
}
}
Tried to understand the concept from these links,
Link1
Link2
You have to start a new coroutine to call collect because the coroutine will keep collecting values until its Job gets cancelled. Don't use runBlocking builder for that, use launch builder instead:
private fun setUpListener() = launch {
firebasePhoneVerificationListener.phoneVerificationFailed.collect {
println("verificatio $it")
}
}
Now to make it work you need to implement CoroutineScope interface in your class. You can do it like this:
class OTPVerificationViewModal1(
private val firebasePhoneVerificationListener: FirebaseOTPVerificationOperation1
): CoroutineScope by CoroutineScope(Dispatchers.Default) {
...
}
If you run it now you will get this output:
verificatio 34567
verificatio failed

Kotlinx Serialization, avoid crashes on other datatype

I'm using external API in the app, while deserialisation is done with Kotlinx Serialization package, i'm facing issues when api result is Array of Int for multiple values and primitive int for single value. How can i avoid crash in this process. Is there better approach to avoid crashes or creating data classes
ex:
import kotlinx.serialization.Serializable
#Serializable
data class Bookings (val slots: List<Int>)
when slots is having single value API returns {slots: 1}
when slots is having multiple value API return { slots: [1,2,3,4]}
It can be done with custom serializer:
import kotlinx.serialization.*
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonInput
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.int
#Serializable(with = BookingsSerializer::class)
data class Bookings(val slots: List<Int>)
#Serializer(forClass = Bookings::class)
object BookingsSerializer : KSerializer<Bookings> {
override fun deserialize(decoder: Decoder): Bookings {
val json = (decoder as JsonInput).decodeJson().jsonObject
return Bookings(parseSlots(json))
}
private fun parseSlots(json: JsonObject): List<Int> {
val slotsJson = json["slots"] ?: return emptyList()
return try {
slotsJson.jsonArray.content.map { it.int }
} catch (e: Exception) {
listOf(slotsJson.int)
}
}
}
#ImplicitReflectionSerializer
fun main() {
val json = """{"slots": 1}"""
val result = Json.parse<Bookings>(json)
println(result) // prints Bookings(slots=[1])
}
I updated #Andrei's answer for 2021 since the class and method names have changed a bit since 2019:
import kotlinx.serialization.*
import kotlinx.serialization.json.*
import kotlinx.serialization.encoding.Decoder
#Serializable(with = BookingsSerializer::class)
data class Bookings(val slots: List<Int>)
#Serializer(forClass = Bookings::class)
object BookingsSerializer : KSerializer<Bookings> {
override fun deserialize(decoder: Decoder): Bookings {
val json = (decoder as JsonDecoder).decodeJsonElement().jsonObject
return Bookings(parseSlots(json))
}
private fun parseSlots(json: JsonObject): List<Int> {
val slotsJson = json["slots"] ?: return emptyList()
return try {
slotsJson.jsonArray.map { it.jsonPrimitive.int }
} catch (e: Exception) {
listOf(slotsJson.jsonPrimitive.int)
}
}
}
val json = """{"slots": 1}"""
val result = Json.decodeFromString<Bookings>(json)
println(result.toString()) // prints Bookings(slots=[1])

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