DEV Community

Cover image for From PHP to Kotlin - Day 4 - Validating a Value object with enum and parametrized tests
Fernando Aparicio
Fernando Aparicio

Posted on

From PHP to Kotlin - Day 4 - Validating a Value object with enum and parametrized tests

The interesting thing about Enums in Kotlin, is that it's too easy add context to them.

Even if Enums in Kotlin has a los of possibilities, we'll use just the basic and necessary for our needs.

Although Enums in Kotlin have many possibilities, we will use what is just and necessary for our needs.

To see it, we'll use the MowerOrientation Value object.

As always, we set up a test scenario to start working.

import mower.mower.exception.InvalidOrientationException
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource
import kotlin.test.Test

private const val ORIENTATION = "N"
private const val INVALID_ORIENTATION = "H"

internal class MowerOrientationTest {
    @Test
    fun `Should be build`() {
        val orientation = MowerOrientation.build(ORIENTATION)

        assertInstanceOf(MowerOrientation::class.java, orientation)
        assertThat(orientation.value).isEqualTo(ORIENTATION)
    }

    @ParameterizedTest(name = "{index} => orientation = ''{0}''")
    @ValueSource(strings = [ "N", "S", "E", "W" ])
    fun `Should be build with valid orientations`(value: String) {
        val orientation = MowerOrientation.build(value)

        assertThat(orientation.value).isEqualTo(value)
    }

    @Test
    fun `Should throw exception for invalid Orientation`() {

        assertThrows(InvalidOrientationException::class.java) {
            MowerOrientation.build(INVALID_ORIENTATION)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

We want to validate that the value we pass to the value object satisfies all four possible directions. We also want a custom error and the code remains elegant (without try-catch)

Notice how the test is configured with several possible values.
The ParameterizedTest in JUnit is the equivalent of the PHPUnit DataProvider. We can add context to it by launching them with a template like the one described in the test.

...
    @ParameterizedTest(name = "{index} => orientation = ''{0}''")
...
Enter fullscreen mode Exit fullscreen mode

Context in tests

The required object can be articulated as follows.

import mower.mower.exception.InvalidOrientationException

@JvmInline
value class MowerOrientation private constructor(val value: String) {
    private enum class Compass(val value: String){
        NORTH("N"),
        EAST("E"),
        SOUTH("S"),
        WEST("W")
    }

    init {
        if(null === enumValues<Compass>().find { it.value == value}){
            throw InvalidOrientationException.withValues(value, Orientations.values().map { it.value }.toString())
        }
    }

    @JvmStatic
    companion object {
        fun build(value: String): MowerOrientation
        {
            return MowerOrientation(value)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

The most interesting thing is that we can create a constant, which serves as a context, and an associated value, which is what the system will use to initialize the value of the value object. There are more ways to feed the parameters in the documentation that I put below.

Ongoing dilemma...

Kotlin is more in favor of throwing exceptions than using the null value as a response. We can do some changes to meet your requirements. We would save ourselves the cost of processing with "find" and "map" in case of responding with an error.

Although I prefer the first implementation, I understand that if we don't handle null values, we can have a hidden problem in our code, something that doesn't happen in an unhandled exception.

    ...
    private enum class Compass{ N, E, S, W }

    init {
        try {
            Compass.valueOf(value)
        } catch (exception: IllegalArgumentException) {
            throw InvalidOrientationException.withValues(value, Compass.values().contentToString())
        }
    }
    ...
Enter fullscreen mode Exit fullscreen mode

If later we want to use enum to retrieve information, in the first way we must use "find" and treat the null value, which is not optimal. For me is the eternal dilemma... and there's no hard answer. Depends... like everything...

The worst thing I've seen is an escalation of try-catch between objects that calling other objects, and that usually ends badly... hard to read and manage.

In this case I think that it's more accurate to go the Kotlin way and we'll leave you with the exception handling. Since later we'll have to use the Enum values for other things and we would be doing "find" every time, thus hampering the utility funcionality.


Links and resources

Enum class

https://kotlinlang.org/docs/enum-classes.html

https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/enum-values.html

Parameterized test sources

https://junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests-sources

https://www.petrikainulainen.net/programming/testing/junit-5-tutorial-writing-parameterized-tests/


Discussion (0)