Search code examples
unit-testingkotlinjunitjunit4

assertEquals fails for Error implementation but pass for Success one


I have these sealed interface

sealed interface Result<out T> {
    data class Success<T>(val data: T) : Result<T>
    data class Error(val exception: Throwable? = null) : Result<Nothing>
}

when i tried to assertEquals the Success one, it pass. But when it comes to Error one, it will fail even though the content is identical. Here is simple example:

@Test
fun testSucess() = runTest {
    whenever(repository.login("email", "password"))
        .thenReturn(someValue)

    val expected = Result.Success(data = someValue)
    val actual = loginUseCase(LoginRequest("email", "password"))

    verify(repository).login("email", "password")
    assertEquals(expected, actual) // this will pass
}

@Test
fun testError() = runTest {
    val exception = RuntimeException("HTTP Error")
    whenever(repository.login("", ""))
        .thenThrow(exception)

    val expected = Result.Error(exception = exception)
    val actual = loginUseCase(LoginRequest("", ""))

    verify(repository).login("", "")
    assertEquals(expected, actual) // this will fail
    assertEquals(expected.toString(), actual.toString()) // this will pass
}

What is causing this and what is possible solution to this? I have read some info that it needs equals() to be overriden, but i still confused as to why it only happens in Error case only and how to properly override the equals method.


Solution

  • Data classes in Kotlin have an implicitly generated equals function automatically derived from all their properties.

    The problem you are facing is probably due to the fact that the type of your someValue has a proper equals function, so the equals works for your Success and its property value. But Throwable does not have an equals function which means that two Throwables are only equal if they are the same instance, which is obviously not the case for expected and actual in your test assertion. I can only guess that in loginUseCase, the exception is wrapped inside another exception, or a new exception is created based on the one thrown by the repository?

    Kotlin already has a built-in Result type, and I strongly recommend using that one instead of defining your own.

    Nonetheless, if you use the built-in type, you will probably face the same problem, since the equals check still fails for the different exception instances.

    There are several ways to solve that:

    1. Define your own exception type and override the equals function to return true if they are both of the same type and have the same message.
    2. Check for expected is Error (or with the default Result type that expected.isFailure), and then check that the messages are the same.
    3. Make sure that loginUseCase throws exactly the same exception instance as is thrown by the repository.