Add support for resolving Kotlin inline value class parameters
Note: this might generalize to Kotlin value classes?
Steps to reproduce
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.MethodSource
class ResultTest {
/**
* This test passes.
* Good pass: .getOrThrow() returns the expected type and value.
*/
@Test
fun normal() {
val result: Result<String> = Result.success("something")
val actual = result.getOrThrow()
assertEquals("something", actual)
}
/**
* This test passes.
* Good pass: the cast is invalid and therefore .getOrThrow() should throw as it does.
*/
@Test
fun cast() {
val result: Result<String> = Result.success("something")
@Suppress("UNCHECKED_CAST")
val castResult = result as Result<Result<String>>
assertThrows<ClassCastException> {
val actual = castResult.getOrThrow()
}
}
/**
* This test passes.
* Good pass: direct calling the method returns the right type.
* This to me proves that the issue is somewhere inside @ParameterizedTest handling.
*/
@Test
fun direct() {
val args = valueProviderFull()
@Suppress("UNCHECKED_CAST")
val result: Result<String> = args.single().get().single() as Result<String>
val actual = result.getOrThrow()
assertEquals("something", actual)
}
/**
* This test passes.
* Good pass: the type of the parameter matches the type of the value provided as the argument from method source.
*/
@MethodSource("valueProviderRaw")
@ParameterizedTest
fun parameterizedRaw(value: String) {
val result: Result<String> = Result.success(value)
val actual = result.getOrThrow()
assertEquals("something", actual)
}
/**
* This test errors with:
* > java.lang.ClassCastException: class kotlin.Result cannot be cast to class java.lang.String.
* This test should pass, because the argument from the method source is a Result<String>.
*/
@MethodSource("valueProviderFull")
@ParameterizedTest
fun parameterizedFull(result: Result<String>) {
val actual = result.getOrThrow()
assertEquals("something", actual)
}
/**
* This test passes.
* This test should fail when trying to call `castResult.getOrThrow()`.
*/
@MethodSource("valueProviderFull")
@ParameterizedTest
fun parameterizedFullCast(result: Result<String>) {
@Suppress("UNCHECKED_CAST")
val castResult = result as Result<Result<String>>
val actual = castResult.getOrThrow()
assertEquals(Result.success("something"), actual)
assertEquals("something", actual.getOrThrow())
}
/**
* This test passes.
* This test should fail when trying to call `result.getOrThrow()`,
* because the provided argument is a Result<String>.
*/
@MethodSource("valueProviderFull")
@ParameterizedTest
fun parameterizedFullMistyped(result: Result<Result<String>>) {
val actual = result.getOrThrow()
assertEquals(Result.success("something"), actual)
assertEquals("something", actual.getOrThrow())
}
companion object {
@JvmStatic
fun valueProviderRaw() = listOf(
Arguments.of("something"),
)
@JvmStatic
fun valueProviderFull() = listOf(
Arguments.of(Result.success("something")),
)
}
}
Context
- Used versions (Jupiter/Vintage/Platform): 5.12.0, 5.13.0, 5.14.0, 6.0.0
- Build Tool/IDE: Gradle
- JVM: 17, 21
- Kotlin: 2.0.0, 2.1.0, 2.2.20
Deliverables
- [ ] Tests should fail/pass as indicated in comments.
Same applies to @ArgumentsSource(MyArgumentsProvider::class)
class MyArgumentsProvider : ArgumentsProvider {
override fun provideArguments(context: ExtensionContext) = Stream.of(
Arguments.of(Result.success("something")),
)
}
Yes those. The inline part is implied, because:
e: [VALUE_CLASS_WITHOUT_JVM_INLINE_ANNOTATION] Value classes without '@JvmInline' annotation are not yet supported.
But also, the generics might be important here. Didn't try with a custom value class yet.
While I can understand the expectation that using Kotlin (inline) value classes should "just work", judging by similar similar issues in other projects (e.g. https://github.com/spring-projects/spring-framework/issues/31698), adding support for them is not trivial.
After some preliminary research, it seems we can get it to work when calling test methods having inline value class parameters via kotlin-reflect which is currently an optional dependency.
POC: https://github.com/junit-team/junit-framework/tree/marc/5081-kotlin-value-class-support
@TWiStErRob Would you be interested in working on this? It definitely needs more tests, including using inline value class parameters and return types in Java etc.
Hi! I’d like to work on this issue. Could you please assign it to me? @marcphilipp
@Ogu1208 Sure! Please let us know if you have any questions or want to discuss anything.
@marcphilipp id like to work on this issue if @Ogu1208 isnt working on it.
@rishabhjain1712
@Ogu1208 Are you working on it?
@marcphilipp @rishabhjain1712 Sorry for the delay! I'm currently working on this issue and will submit a PR soon.