Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding new assertFailureWith #525

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions assertk/src/commonMain/kotlin/assertk/assert.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package assertk

import assertk.assertions.isInstanceOf
import assertk.assertions.support.display
import assertk.assertions.support.show
import kotlin.reflect.KProperty0
Expand Down Expand Up @@ -190,3 +191,13 @@ inline fun assertFailure(f: () -> Unit): Assert<Throwable> {
}
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 <reified T: Throwable> assertFailureWith(f: () -> Unit): Assert<T> {
val assertFailure = assertFailure(f)
assertFailure.isInstanceOf(T::class)
@Suppress("UNCHECKED_CAST")
return assertFailure as Assert<T>
Comment on lines +200 to +202
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This returns a specialized Assert with the new type

Suggested change
assertFailure.isInstanceOf(T::class)
@Suppress("UNCHECKED_CAST")
return assertFailure as Assert<T>
return assertFailure.isInstanceOf(T::class)

}
13 changes: 12 additions & 1 deletion assertk/src/commonMain/kotlin/assertk/assertions/throwable.kt
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -62,5 +62,16 @@ fun Assert<Throwable>.hasRootCause(cause: Throwable) {
}
}

/**
* Asserts the throwable with a specific type have the expected properties for it.
*/
fun <T: Throwable> Assert<T>.hasProperties(vararg pairs: Pair<KProperty1<T, Any>, Any>) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unrelated to assertFailsWith. It's not clear why it's type parameter is bound to Throwable subtypes since it's a general-purpose utility. I would separate it into its own PR, or even start with an issue discussing the use case and why regular .all { } in insufficient.

all {
pairs.forEach {
prop(it.first).isEqualTo(it.second)
}
}
}

private fun Throwable.rootCause(): Throwable = this.cause?.rootCause() ?: this

31 changes: 25 additions & 6 deletions assertk/src/commonTest/kotlin/test/assertk/AssertFailureTest.kt
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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 {

Expand All @@ -24,6 +20,29 @@ class AssertFailureTest {
assertSame(expected, assertFailure { throw expected }.valueOrFail)
}

@Test
fun failure_with_expected_type() {
val expected = IllegalArgumentException()
val actual = assertFailureWith<IllegalArgumentException> { throw expected }
assertSame(expected, actual.valueOrFail)
}

@Test
fun failure_with_wrong_type() {
val t = assertFailsWith<AssertionFailedError> {
assertFailureWith<IllegalArgumentException> {
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<AssertionFailedError> {
Expand Down
Original file line number Diff line number Diff line change
@@ -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() {
Expand Down Expand Up @@ -147,4 +150,38 @@ class ThrowableTest {
)
}
//endregion

//region hasProperties
@Test
fun hasProperties_single_fail() {
val exception = DummyException(1116, 12.5)
val error = assertFailsWith<AssertionError> {
assertFailureWith<DummyException> { 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<AssertionError> {
assertFailureWith<DummyException> { 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
}