Search code examples
kotlinunit-testingexception

Unit test for exception assertion fails despite expected and actual value matching a sad path


I have a function below that throws exception when failed to deserialize:

fun deserialize(payload: SdkBytes): List<Event>? {
    return try {
        val response = Json.decodeFromString<Response>(payload.asUtf8String())
        return response.body
    } catch (e: IllegalArgumentException) {
        // if fails to deserialize
        logger.error(e)
        null
    }
}

I have the following unit test to test when the exception is thrown:

fun testWhenMalformedJson() {
    val body = "test body"
    val bytes = SdkBytes.fromUtf8String(body)

    expectCatching { deserialize(bytes) }
        .isFailure()
        .isA<IllegalArgumentException>()
}

When I try to run the test it fails with the following error:

  ▼ Expect that Success(null):
    ✗ is failure
      returned null

  Expected :null
  Actual   :null

I am unable to understand why my test is failing if actual and expected values are same.


Solution

  • First, it is recommended to use SerializationException to catch any decoding-specific errors because IllegalArgumentException is a generic exception that can also be thrown if the decoded input is not a valid instance of T. See: kotlinx.serialization.json - decodeFromString

    In regard to the test, it is failing for the correct reason. No exception is being thrown from your deserialize method because it is being caught by the catch block.

    Instead, one way to test this logic would be asserting if null is being returned. (assuming response.body returns a non-nullable type).

    fun deserialize(payload: String): List<String>? {
        return try {
            return Json.decodeFromString<List<String>>(payload)
        } catch (e: SerializationException) {
            null // if fails to deserialize
        }
    }
    
    @Test
    fun `should return null when an exception is thrown`() {
        val payload = "invalid"
        val response = deserialize(payload)
        assert(response == null)
    }
    
    @Test
    fun `should return a list when deserialization is successful`() {
        val payload = "[\"a\", \"b\", \"c\"]"
        val response = deserialize(payload)
        assert(response != null)
    }
    

    If you want to be more granular, you can static mockk the Json decoder to throw an exception using a library like Mockk to ensure null can only be returned by the catch block:

    import io.mockk.every
    import io.mockk.mockkObject
    import kotlinx.serialization.SerializationException
    import kotlinx.serialization.decodeFromString
    import kotlinx.serialization.json.Json
    import org.junit.jupiter.api.Test
    
    @Test
    fun `should return null when an exception is thrown`() {
        mockkObject(Json.Default) {
            every { Json.decodeFromString<List<String>>(any()) } throws SerializationException("invalid")
            val response = deserialize("any string...")
            assert(response == null)
        }
    }