3

I try to implement a data driven test for a kotlin function that has several parameters with default values.

In my test definitions I'd like to be able to leave out any combination of arguments that have a default argument in the function declaration. I don't see how that can work (without a separate branch for each combination of default values).

Maybe it is better explained by code:

import kotlin.test.assertEquals

fun foobalize(start: Int = 0, separator: String = "\t", convert: Boolean = false): Int {
    return 0 // implementation omitted
}

data class TestSpec(
        val start: Int? = null, // null should mean: Don't pass this argument to foobalize(), but use its default value
        val separator: String? = null, // dito
        val convert: Boolean? = null, // dito
        val expectedResult: Int
)

fun testFoobalize(testSpec: TestSpec) {
    // How to call foobalize here with values from TestSpec, but leave out parameters that are null,
    // so that the defaults from the fopobalize() function declaration are used???
    val actualResult = foobalize(start = testSpec.start)
    assertEquals(testSpec.expectedResult, actualResult)
}

Is there some completely different way to do this?

eekboom
  • 5,551
  • 1
  • 30
  • 39
  • If you don't want to check if the compiler does it's thing, and you don't mind the maintenance burden, then conditionizing the call by tests like: if (testSpec.start==null) {testSpec.start=0}, and always calling the function with all arguments would work. Does that work for you? – g_bor Sep 09 '19 at 20:08
  • @g_bor Main problem with that: It does not check that default arguments have the values that they do. So if somebody changes a default argument in foobalize() the test will still be ok, but other code may have become broken. Also I now have specified the default argument values in two places which is not DRY. (And btw, in that case I could just specify the same default values in the primary constructor of TestSpec, right?) – eekboom Sep 09 '19 at 20:16
  • Yes, that is all correct, this is why I referred to the associated maintenance burden. And yes, you could specify it in the TestSpec constructor, if that is more readable in your case. I did not mean this as a solution, that's why I wrote it in a comment. – g_bor Sep 09 '19 at 20:22
  • It doesn't seem possible. 1) Defaults parameters just create different overloads. 2) Kotlin decides which overload to call at compile time. 3) You want to decide which function to call at runtime. –  Sep 09 '19 at 20:22
  • https://stackoverflow.com/questions/53912047/two-additional-types-in-default-constructor-in-kotlin might be relevant. It seems there is a bitmask specifying which arguments should take default values. – g_bor Sep 09 '19 at 20:29
  • Builder pattern will work here for you. As you have already a data class ,create a build method inside it which returns instance of finalize and also make finalize constructor private. – Ashok Kumar Sep 10 '19 at 02:13

1 Answers1

1

Default parameters is a kotlin compiler feature, so there is no easy way to use it at runtime. It should be possible with reflection though.

IMO if you really want it this way, it's better to enhance the API with another method, which takes a data class as a parameter.

data classc Spec(val start: Int = 0, val separator: String = "\t", val convert: Boolean = false)

fun foobalize(start: Int, separator: String, convert: Boolean): Int {}

fun foobalize(spec: Spec) = foobalize(spec.start, spec.separator, spec.convert)
esentsov
  • 6,372
  • 21
  • 28