diff --git a/libraries/kotlin.test/common/src/main/kotlin/kotlin/test/Assertions.kt b/libraries/kotlin.test/common/src/main/kotlin/kotlin/test/Assertions.kt index c5a874b181f..789d73f2988 100644 --- a/libraries/kotlin.test/common/src/main/kotlin/kotlin/test/Assertions.kt +++ b/libraries/kotlin.test/common/src/main/kotlin/kotlin/test/Assertions.kt @@ -17,6 +17,7 @@ import kotlin.internal.* import kotlin.jvm.JvmName import kotlin.native.concurrent.ThreadLocal import kotlin.reflect.KClass +import kotlin.reflect.typeOf /** * Current adapter providing assertion implementations @@ -76,6 +77,31 @@ fun <@OnlyInputTypes T> assertNotSame(illegal: T, actual: T, message: String? = asserter.assertNotSame(message, illegal, actual) } +/** + * Asserts that [value] is of type [T], with an optional [message]. + * + * Note that due to type erasure the type check may be partial (e.g. assertIs>(value) + * only checks for the class being [List] and not the type of its elements because it's erased). + */ +@SinceKotlin("1.5") +@InlineOnly +inline fun assertIs(value: Any?, message: String? = null) { + contract { returns() implies (value is T) } + asserter.assertTrue({ messagePrefix(message) + "Expected value to be of type <${typeOf()}>, actual <${value?.let { it::class }}>." }, value is T) +} + +/** + * Asserts that [value] is not of type [T], with an optional [message]. + * + * Note that due to type erasure the type check may be partial (e.g. assertNotIs>(value) + * only checks for the class being [List] and not the type of its elements because it's erased). + */ +@SinceKotlin("1.5") +@InlineOnly +inline fun assertNotIs(value: Any?, message: String? = null) { + asserter.assertFalse({ messagePrefix(message) + "Expected value to not be of type <${typeOf()}>" }, value is T) +} + /** Asserts that the [actual] value is not `null`, with an optional [message]. */ fun assertNotNull(actual: T?, message: String? = null): T { contract { returns() implies (actual != null) } diff --git a/libraries/kotlin.test/common/src/main/kotlin/kotlin/test/Utils.kt b/libraries/kotlin.test/common/src/main/kotlin/kotlin/test/Utils.kt index c8b0e232a7f..c6bb6f14301 100644 --- a/libraries/kotlin.test/common/src/main/kotlin/kotlin/test/Utils.kt +++ b/libraries/kotlin.test/common/src/main/kotlin/kotlin/test/Utils.kt @@ -5,6 +5,7 @@ package kotlin.test +@PublishedApi // called from inline assert functions internal fun messagePrefix(message: String?) = if (message == null) "" else "$message. " internal expect fun lookupAsserter(): Asserter diff --git a/libraries/kotlin.test/common/src/test/kotlin/kotlin/test/tests/BasicAssertionsTest.kt b/libraries/kotlin.test/common/src/test/kotlin/kotlin/test/tests/BasicAssertionsTest.kt index 1780275817b..799e12544aa 100644 --- a/libraries/kotlin.test/common/src/test/kotlin/kotlin/test/tests/BasicAssertionsTest.kt +++ b/libraries/kotlin.test/common/src/test/kotlin/kotlin/test/tests/BasicAssertionsTest.kt @@ -205,6 +205,33 @@ class BasicAssertionsTest { fun testExpectFails() { checkFailedAssertion { expect(1) { 2 } } } + + @Test + fun testAssertIs() { + val s: Any = "test" + assertIs(s) + assertEquals(4, s.length) + } + + @Test + fun testAssertIsFails() { + val error = checkFailedAssertion { assertIs("test") } + assertTrue(error.message.startsWith("Expected value to be of type")) + assertTrue(error.message.contains("Int")) + assertTrue(error.message.contains("String")) + } + + @Test + fun testAssertNotIs() { + assertNotIs("test") + } + + @Test + fun testAssertNotIsFails() { + val error = checkFailedAssertion { assertNotIs(1) } + assertTrue(error.message.startsWith("Expected value to not be of type")) + assertTrue(error.message.contains("Int")) + } }