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

Allow override of assertion context's error message display. #313

Merged
merged 1 commit into from
Aug 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
33 changes: 26 additions & 7 deletions assertk/src/commonMain/kotlin/assertk/assert.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package assertk

import assertk.assertions.isFailure
import assertk.assertions.isSuccess
import assertk.assertions.support.display
import assertk.assertions.support.show
import kotlin.contracts.contract
import kotlin.reflect.KProperty0

/**
Expand All @@ -17,7 +17,7 @@ annotation class AssertkDsl
* @see [assertThat]
*/
@AssertkDsl
sealed class Assert<out T>(val name: String?, internal val context: Any?) {
sealed class Assert<out T>(val name: String?, internal val context: AssertingContext) {
/**
* Transforms an assertion from one type to another. If the assertion is failing the resulting assertion will still
* be failing, otherwise the mapping function is called. An optional name can be provided, otherwise this
Expand Down Expand Up @@ -74,19 +74,30 @@ sealed class Assert<out T>(val name: String?, internal val context: Any?) {
}

@PublishedApi
internal class ValueAssert<out T>(val value: T, name: String?, context: Any?) :
internal class ValueAssert<out T>(val value: T, name: String?, context: AssertingContext) :
Assert<T>(name, context) {

override fun <R> assertThat(actual: R, name: String?): Assert<R> =
ValueAssert(actual, name, if (context != null || this.value === actual) context else this.value)
override fun <R> assertThat(actual: R, name: String?): Assert<R> {
val newContext = if (context.originatingSubject != null || this.value == actual) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Passing only a flag was not enough, since the original implementation ran this comparison to determine whether an Assert is considered "transformed". For this reason, I introduced an internal type AssertingContext that simply holds the original value along with its display function.

context
} else {
context.copy(originatingSubject = this.value)
}
return ValueAssert(actual, name, newContext)
}
}

@PublishedApi
internal class FailingAssert<out T>(val error: Throwable, name: String?, context: Any?) :
internal class FailingAssert<out T>(val error: Throwable, name: String?, context: AssertingContext) :
Assert<T>(name, context) {
override fun <R> assertThat(actual: R, name: String?): Assert<R> = FailingAssert(error, name, context)
}

internal data class AssertingContext(
val originatingSubject: Any? = null,
val displayOriginatingSubject: () -> String
)

/**
* Runs the given lambda if the block throws an error, otherwise fails.
*/
Expand Down Expand Up @@ -182,7 +193,15 @@ internal expect fun showError(e: Throwable): String
* assertThat(true, name = "true").isTrue()
* ```
*/
fun <T> assertThat(actual: T, name: String? = null): Assert<T> = ValueAssert(actual, name, null)
fun <T> assertThat(
actual: T,
name: String? = null,
displayActual: (T) -> String = { display(it) }
): Assert<T> = ValueAssert(
value = actual,
name = name,
context = AssertingContext { displayActual(actual) }
)

/**
* Asserts on the given property reference using its name, if no explicit name is specified. This method
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ fun <T> Assert<T>.fail(expected: Any?, actual: Any?) {
*/
fun <T> Assert<T>.expected(message: String, expected: Any? = NONE, actual: Any? = NONE): Nothing {
val maybeSpace = if (message.startsWith(":")) "" else " "
val maybeInstance = if (context != null) " ${show(context, "()")}" else ""
val maybeInstance = if (context.originatingSubject != null) " (${context.displayOriginatingSubject()})" else ""
fail(
message = "expected${formatName(name)}$maybeSpace$message$maybeInstance",
expected = expected,
Expand Down
9 changes: 9 additions & 0 deletions assertk/src/commonTest/kotlin/test/assertk/AssertTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,15 @@ class AssertTest {
assertEquals("expected [test]:<[2]> but was:<[1]> (0)", error.message)
}

@Test fun assertThat_failing_transformed_assert_shows_original_by_displayActual_lambda() {
val error = assertFails {
assertThat(0, name = "test", displayActual = { "Number:${it}" })
.assertThat(1).isEqualTo(2)
}

assertEquals("expected [test]:<[2]> but was:<[1]> (Number:0)", error.message)
}

@Test fun assertThat_on_failing_assert_is_ignored() {
val error = assertFails {
assertAll {
Expand Down