diff --git a/assertk/src/commonMain/kotlin/assertk/assert.kt b/assertk/src/commonMain/kotlin/assertk/assert.kt index 548938ae..e6cdb633 100644 --- a/assertk/src/commonMain/kotlin/assertk/assert.kt +++ b/assertk/src/commonMain/kotlin/assertk/assert.kt @@ -1,5 +1,6 @@ package assertk +import assertk.assertions.isInstanceOf import assertk.assertions.support.display import assertk.assertions.support.show import kotlin.reflect.KProperty0 @@ -190,3 +191,13 @@ inline fun assertFailure(f: () -> Unit): Assert { } fail("expected failure but lambda completed successfully") } + +/** + * Asserts that the given block will throw an exception with expected type rather than complete successfully. + */ +inline fun assertFailureWith(f: () -> Unit): Assert { + val assertFailure = assertFailure(f) + assertFailure.isInstanceOf(T::class) + @Suppress("UNCHECKED_CAST") + return assertFailure as Assert +} \ No newline at end of file diff --git a/assertk/src/commonMain/kotlin/assertk/assertions/throwable.kt b/assertk/src/commonMain/kotlin/assertk/assertions/throwable.kt index 036d437f..fbc37c92 100644 --- a/assertk/src/commonMain/kotlin/assertk/assertions/throwable.kt +++ b/assertk/src/commonMain/kotlin/assertk/assertions/throwable.kt @@ -1,8 +1,8 @@ package assertk.assertions import assertk.Assert -import assertk.ValueAssert import assertk.all +import kotlin.reflect.KProperty1 /** * Returns an assert on the throwable's message. @@ -62,5 +62,16 @@ fun Assert.hasRootCause(cause: Throwable) { } } +/** + * Asserts the throwable with a specific type have the expected properties for it. + */ +fun Assert.hasProperties(vararg pairs: Pair, Any>) { + all { + pairs.forEach { + prop(it.first).isEqualTo(it.second) + } + } +} + private fun Throwable.rootCause(): Throwable = this.cause?.rootCause() ?: this diff --git a/assertk/src/commonTest/kotlin/test/assertk/AssertFailureTest.kt b/assertk/src/commonTest/kotlin/test/assertk/AssertFailureTest.kt index 48882746..fbd422af 100644 --- a/assertk/src/commonTest/kotlin/test/assertk/AssertFailureTest.kt +++ b/assertk/src/commonTest/kotlin/test/assertk/AssertFailureTest.kt @@ -1,6 +1,7 @@ package test.assertk import assertk.assertFailure +import assertk.assertFailureWith import assertk.assertions.isEqualTo import assertk.assertions.isInstanceOf import assertk.assertions.message @@ -9,12 +10,7 @@ import test.assertk.assertions.valueOrFail import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException import kotlin.coroutines.suspendCoroutine -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertFalse -import kotlin.test.assertSame -import kotlin.test.assertTrue +import kotlin.test.* class AssertFailureTest { @@ -24,6 +20,29 @@ class AssertFailureTest { assertSame(expected, assertFailure { throw expected }.valueOrFail) } + @Test + fun failure_with_expected_type() { + val expected = IllegalArgumentException() + val actual = assertFailureWith { throw expected } + assertSame(expected, actual.valueOrFail) + } + + @Test + fun failure_with_wrong_type() { + val t = assertFailsWith { + assertFailureWith { + throw IllegalStateException() + } + } + + val message = t.message ?: fail("should have a message") + + assertTrue("expected to be instance of" in message) + assertTrue("IllegalArgumentException" in message) + assertTrue("but had class" in message) + assertTrue("IllegalStateException" in message) + } + @Test fun failure_originating_subject_not_wrapped_in_result() { val t = assertFailsWith { diff --git a/assertk/src/commonTest/kotlin/test/assertk/assertions/ThrowableTest.kt b/assertk/src/commonTest/kotlin/test/assertk/assertions/ThrowableTest.kt index 008cc9bd..b66c1baa 100644 --- a/assertk/src/commonTest/kotlin/test/assertk/assertions/ThrowableTest.kt +++ b/assertk/src/commonTest/kotlin/test/assertk/assertions/ThrowableTest.kt @@ -1,16 +1,19 @@ package test.assertk.assertions +import assertk.assertFailureWith import assertk.assertThat import assertk.assertions.* import test.assertk.exceptionPackageName import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith +import kotlin.test.assertTrue +import kotlin.test.fail class ThrowableTest { - val rootCause = Exception("rootCause") - val cause = Exception("cause", rootCause) - val subject = Exception("test", cause) + private val rootCause = Exception("rootCause") + private val cause = Exception("cause", rootCause) + private val subject = Exception("test", cause) @Test fun extracts_message() { @@ -147,4 +150,38 @@ class ThrowableTest { ) } //endregion + + //region hasProperties + @Test + fun hasProperties_single_fail() { + val exception = DummyException(1116, 12.5) + val error = assertFailsWith { + assertFailureWith { throw exception }.hasProperties( + DummyException::index to 1118, + DummyException::rate to 12.5 + ) + } + assertEquals( + "expected [index]:<111[8]> but was:<111[6]> ($exception)", + error.message + ) + } + + @Test + fun hasProperties_multiple_fails() { + val exception = DummyException(1116, 12.5) + val error = assertFailsWith { + assertFailureWith { throw exception }.hasProperties( + DummyException::index to 1118, + DummyException::rate to 15.3 + ) + } + val message = error.message ?: fail("should have a message") + assertTrue("The following assertions failed (2 failures)" in message) + assertTrue("expected [index]:<111[8]> but was:<111[6]> ($exception)" in message) + assertTrue("expected [rate]:<1[5.3]> but was:<1[2.5]> ($exception)" in message) + } + + class DummyException(val index: Int, val rate: Double): Exception("bad value: $index -> $rate") + //endregion }