From 3d8455b257bdf903259676a04329fad28676c970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Deleuze?= Date: Tue, 21 Feb 2023 10:16:23 +0100 Subject: [PATCH] Add unit tests for CoroutinesUtils Closes gh-29968 --- spring-core/spring-core.gradle | 1 + .../core/KotlinCoroutinesUtilsTests.kt | 143 ++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 spring-core/src/test/kotlin/org/springframework/core/KotlinCoroutinesUtilsTests.kt diff --git a/spring-core/spring-core.gradle b/spring-core/spring-core.gradle index 086977e8c1..b9e94cfba9 100644 --- a/spring-core/spring-core.gradle +++ b/spring-core/spring-core.gradle @@ -86,6 +86,7 @@ dependencies { testImplementation("com.squareup.okhttp3:mockwebserver") testImplementation("org.jetbrains.kotlinx:kotlinx-serialization-json") testImplementation("com.fasterxml.jackson.core:jackson-databind") + testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor") testFixturesImplementation("com.google.code.findbugs:jsr305") testFixturesImplementation("org.junit.platform:junit-platform-launcher") testFixturesImplementation("org.junit.jupiter:junit-jupiter-api") diff --git a/spring-core/src/test/kotlin/org/springframework/core/KotlinCoroutinesUtilsTests.kt b/spring-core/src/test/kotlin/org/springframework/core/KotlinCoroutinesUtilsTests.kt new file mode 100644 index 0000000000..27617f7c84 --- /dev/null +++ b/spring-core/src/test/kotlin/org/springframework/core/KotlinCoroutinesUtilsTests.kt @@ -0,0 +1,143 @@ +/* + * Copyright 2002-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.core + +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.reactor.awaitSingle +import org.assertj.core.api.Assertions +import org.junit.jupiter.api.Test +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono +import reactor.test.StepVerifier +import kotlin.coroutines.Continuation +import kotlin.coroutines.coroutineContext + +class KotlinCoroutinesUtilsTests { + + @Test + fun deferredToMono() { + runBlocking { + val deferred: Deferred = async(Dispatchers.IO) { + delay(10) + "foo" + } + val mono = CoroutinesUtils.deferredToMono(deferred) + StepVerifier.create(mono) + .expectNext("foo") + .expectComplete() + .verify() + } + } + + @Test + fun monoToDeferred() { + runBlocking { + val mono = Mono.just("foo") + val deferred = CoroutinesUtils.monoToDeferred(mono) + Assertions.assertThat(deferred.await()).isEqualTo("foo") + } + } + + @Test + fun invokeSuspendingFunctionWithNullContinuationParameter() { + val method = KotlinCoroutinesUtilsTests::class.java.getDeclaredMethod("suspendingFunction", String::class.java, Continuation::class.java) + val publisher = CoroutinesUtils.invokeSuspendingFunction(method, this, "foo", null) + Assertions.assertThat(publisher).isInstanceOf(Mono::class.java) + StepVerifier.create(publisher) + .expectNext("foo") + .expectComplete() + .verify() + } + + @Test + fun invokeSuspendingFunctionWithoutContinuationParameter() { + val method = KotlinCoroutinesUtilsTests::class.java.getDeclaredMethod("suspendingFunction", String::class.java, Continuation::class.java) + val publisher = CoroutinesUtils.invokeSuspendingFunction(method, this, "foo") + Assertions.assertThat(publisher).isInstanceOf(Mono::class.java) + StepVerifier.create(publisher) + .expectNext("foo") + .expectComplete() + .verify() + } + + @Test + fun invokeNonSuspendingFunction() { + val method = KotlinCoroutinesUtilsTests::class.java.getDeclaredMethod("nonSuspendingFunction", String::class.java) + Assertions.assertThatIllegalArgumentException().isThrownBy { CoroutinesUtils.invokeSuspendingFunction(method, this, "foo") } + } + + @Test + fun invokeSuspendingFunctionWithFlow() { + val method = KotlinCoroutinesUtilsTests::class.java.getDeclaredMethod("suspendingFunctionWithFlow", Continuation::class.java) + val publisher = CoroutinesUtils.invokeSuspendingFunction(method, this) + Assertions.assertThat(publisher).isInstanceOf(Flux::class.java) + StepVerifier.create(publisher) + .expectNext("foo") + .expectNext("bar") + .expectComplete() + .verify() + } + + @Test + fun invokeSuspendingFunctionWithNullContinuationParameterAndContext() { + val method = KotlinCoroutinesUtilsTests::class.java.getDeclaredMethod("suspendingFunctionWithContext", String::class.java, Continuation::class.java) + val context = CoroutineName("name") + val mono = CoroutinesUtils.invokeSuspendingFunction(context, method, this, "foo", null) as Mono + runBlocking { + Assertions.assertThat(mono.awaitSingle()).isEqualTo("foo") + } + } + + @Test + fun invokeSuspendingFunctionWithoutContinuationParameterAndContext() { + val method = KotlinCoroutinesUtilsTests::class.java.getDeclaredMethod("suspendingFunctionWithContext", String::class.java, Continuation::class.java) + val context = CoroutineName("name") + val mono = CoroutinesUtils.invokeSuspendingFunction(context, method, this, "foo") as Mono + runBlocking { + Assertions.assertThat(mono.awaitSingle()).isEqualTo("foo") + } + } + + @Test + fun invokeNonSuspendingFunctionWithContext() { + val method = KotlinCoroutinesUtilsTests::class.java.getDeclaredMethod("nonSuspendingFunction", String::class.java) + Assertions.assertThatIllegalArgumentException().isThrownBy { CoroutinesUtils.invokeSuspendingFunction(method, this, "foo") } + } + + suspend fun suspendingFunction(value: String): String { + delay(10) + return value + } + + suspend fun suspendingFunctionWithFlow(): Flow { + delay(10) + return flowOf("foo", "bar") + } + + fun nonSuspendingFunction(value: String): String { + return value + } + + suspend fun suspendingFunctionWithContext(value: String): String { + delay(10) + Assertions.assertThat(coroutineContext[CoroutineName]?.name).isEqualTo("name") + return value + } + +}