Skip to content

Commit

Permalink
Unify property extractor function names (#524)
Browse files Browse the repository at this point in the history
- Rename `prop` to `having` with deprecations
- Rename `suspendCall` to `having` with deprecations

---------

Co-authored-by: Joseph Cooper <[email protected]>
  • Loading branch information
grodin and Joseph Cooper authored Apr 18, 2024
1 parent 6533918 commit 6b65668
Show file tree
Hide file tree
Showing 202 changed files with 518 additions and 480 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

### Changed
- renamed `prop` to `having` as part of effort to unify API naming, old name is deprecated.
- renamed `suspendCall` to `having` as part of effort to unify API naming, old name is deprecated.

## [0.28.1] 2024-04-17

### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,16 @@ import assertk.assertions.support.appendName
* @param extract The suspend function to extract the property value out of the value of the current assert.
*
* ```
* assertThat(person).suspendCall("name()", { it.name() }).isEqualTo("Sue")
* assertThat(person).having("name()", { it.name() }).isEqualTo("Sue")
* ```
*/
suspend fun <T, P> Assert<T>.having(name: String, extract: suspend (T) -> P): Assert<P> =
transform(appendName(name, separator = ".")) { extract(it) }

@Deprecated(
message = "Function suspendCall has been renamed to having",
replaceWith = ReplaceWith("having(name, extract)"),
level = DeprecationLevel.WARNING
)
suspend fun <T, P> Assert<T>.suspendCall(name: String, extract: suspend (T) -> P): Assert<P> =
transform(appendName(name, separator = ".")) { extract(it) }
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ import assertk.assertions.support.expected
import assertk.assertions.support.expectedListDiff
import assertk.assertions.support.show
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.*

suspend fun Assert<Flow<*>>.count(): Assert<Int> = suspendCall("count()", Flow<*>::count)
suspend fun Assert<Flow<*>>.count(): Assert<Int> = having("count()", Flow<*>::count)

/**
* Asserts the flow is empty. Fails as soon as the flow delivers an element.
Expand Down Expand Up @@ -190,4 +189,4 @@ suspend fun Assert<Flow<*>>.containsExactly(vararg elements: Any?) = given { act
}

private class AbortFlowException :
CancellationException("Flow was aborted, no more elements needed")
CancellationException("Flow was aborted, no more elements needed")
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import assertk.assertThat
import assertk.assertions.isEmpty
import assertk.assertions.isEqualTo
import assertk.assertions.isNotNull
import assertk.coroutines.assertions.suspendCall
import assertk.coroutines.assertions.having
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
Expand All @@ -14,22 +14,22 @@ class AnyTest {
val subject = BasicObject("test")

@Test
fun suspendCall_passes() = runTest {
assertThat(subject).suspendCall("str") { it.str() }.isEqualTo("test")
fun having_passes() = runTest {
assertThat(subject).having("str") { it.str() }.isEqualTo("test")
}

@Test
fun suspendCall_includes_name_in_failure_message() = runTest {
fun having_includes_name_in_failure_message() = runTest {
val error = assertFailsWith<AssertionError> {
assertThat(subject).suspendCall("str") { it.str() }.isEmpty()
assertThat(subject).having("str") { it.str() }.isEmpty()
}
assertEquals("expected [str] to be empty but was:<\"test\"> (test)", error.message)
}

@Test
fun nested_suspendCall_include_names_in_failure_message() = runTest {
fun nested_having_include_names_in_failure_message() = runTest {
val error = assertFailsWith<AssertionError> {
assertThat(subject).suspendCall("other") { it.other() }.suspendCall("str") { it?.str() }.isNotNull()
assertThat(subject).having("other") { it.other() }.having("str") { it?.str() }.isNotNull()
}
assertEquals("expected [other.str] to not be null (test)", error.message)
}
Expand Down
42 changes: 34 additions & 8 deletions assertk/src/commonMain/kotlin/assertk/assertions/any.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@ import kotlin.reflect.KProperty1
/**
* Returns an assert on the kotlin class of the value.
*/
fun Assert<Any>.kClass() = prop("class") { it::class }
fun Assert<Any>.kClass() = having("class") { it::class }

/**
* Returns an assert on the toString method of the value.
*/
fun Assert<Any?>.toStringFun() = prop("toString", Any?::toString)
fun Assert<Any?>.toStringFun() = having("toString", Any?::toString)

/**
* Returns an assert on the hasCode method of the value.
*/
fun Assert<Any>.hashCodeFun() = prop("hashCode", Any::hashCode)
fun Assert<Any>.hashCodeFun() = having("hashCode", Any::hashCode)

/**
* Asserts the value is equal to the expected one, using `==`.
Expand Down Expand Up @@ -146,39 +146,64 @@ fun <T : Any> Assert<T?>.isNotNull(): Assert<T> = transform { actual ->
* @param extract The function to extract the property value out of the value of the current assert.
*
* ```
* assertThat(person).prop("name", { it.name }).isEqualTo("Sue")
* assertThat(person).having("name", { it.name }).isEqualTo("Sue")
* ```
*/
fun <T, P> Assert<T>.having(name: String, extract: (T) -> P): Assert<P> =
transform(appendName(name, separator = "."), extract)

@Deprecated(
message = "Function prop has been renamed to having",
replaceWith = ReplaceWith("having(name, extract)"),
level = DeprecationLevel.WARNING
)
fun <T, P> Assert<T>.prop(name: String, extract: (T) -> P): Assert<P> =
transform(appendName(name, separator = "."), extract)


/**
* Returns an assert that asserts on the given property.
*
* Example:
* ```
* assertThat(person).prop(Person::name).isEqualTo("Sue")
* assertThat(person).having(Person::name).isEqualTo("Sue")
* ```
*
* @param property Property on which to assert. The name of this
* property will be shown in failure messages.
*/
fun <T, P> Assert<T>.having(property: KProperty1<T, P>): Assert<P> =
having(property.name) { property.get(it) }

@Deprecated(
message = "Function prop has been renamed to having",
replaceWith = ReplaceWith("having(property)"),
level = DeprecationLevel.WARNING
)
fun <T, P> Assert<T>.prop(property: KProperty1<T, P>): Assert<P> =
prop(property.name) { property.get(it) }
having(property.name) { property.get(it) }

/**
* Returns an assert that asserts on the result of calling the given function.
*
* Example:
* ```
* assertThat(person).prop(Person::nameAsLowerCase).isEqualTo("sue")
* assertThat(person).having(Person::nameAsLowerCase).isEqualTo("sue")
* ```
*
* @param callable Callable on which to assert. The name of this
* callable will be shown in the failure messages.
*/
fun <T, R, F> Assert<T>.having(callable: F): Assert<R> where F : (T) -> R, F : KCallable<R> =
having(callable.name, callable)

@Deprecated(
message = "Function prop has been renamed to having",
replaceWith = ReplaceWith("having(callable)"),
level = DeprecationLevel.WARNING
)
fun <T, R, F> Assert<T>.prop(callable: F): Assert<R> where F : (T) -> R, F : KCallable<R> =
prop(callable.name, callable)
having(callable.name, callable)

/**
* Asserts the value has the expected kotlin class. This is an exact match, so `assertThat("test").hasClass<String>()`
Expand Down Expand Up @@ -219,6 +244,7 @@ fun <T : Any> Assert<T>.doesNotHaveClass(kclass: KClass<out T>) = given { actual
if (kclass != actual::class) return
expected("to not have class:${show(kclass)}")
}

/**
* Asserts the value is not an instance of the expected kotlin class. Both
* `assertThat("test").isNotInstanceOf<String>()` and `assertThat("test").isNotInstanceOf<String>()` fail.
Expand Down
2 changes: 1 addition & 1 deletion assertk/src/commonMain/kotlin/assertk/assertions/array.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import assertk.assertions.support.*
/**
* Returns an assert on the Arrays's size.
*/
fun Assert<Array<*>>.size() = prop("size") { it.size }
fun Assert<Array<*>>.size() = having("size") { it.size }

/**
* Asserts the array contents are equal to the expected one, using [contentDeepEquals].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import assertk.assertions.support.show
/**
* Returns an assert on the CharSequence's length.
*/
fun Assert<CharSequence>.length() = prop("length", CharSequence::length)
fun Assert<CharSequence>.length() = having("length", CharSequence::length)

/**
* Asserts the char sequence is empty.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import assertk.assertions.support.*
/**
* Returns an assert on the Collection's size.
*/
fun Assert<Collection<*>>.size() = prop("size", Collection<*>::size)
fun Assert<Collection<*>>.size() = having("size", Collection<*>::size)

/**
* Asserts the collection is empty.
Expand Down
2 changes: 1 addition & 1 deletion assertk/src/commonMain/kotlin/assertk/assertions/map.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import assertk.assertions.support.show
/**
* Returns an assert on the Maps's size.
*/
fun Assert<Map<*, *>>.size() = prop("size", Map<*, *>::size)
fun Assert<Map<*, *>>.size() = having("size", Map<*, *>::size)

/**
* Asserts the collection is empty.
Expand Down
7 changes: 3 additions & 4 deletions assertk/src/commonMain/kotlin/assertk/assertions/throwable.kt
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
package assertk.assertions

import assertk.Assert
import assertk.ValueAssert
import assertk.all

/**
* Returns an assert on the throwable's message.
*/
fun Assert<Throwable>.message() = prop("message", Throwable::message)
fun Assert<Throwable>.message() = having("message", Throwable::message)

/**
* Returns an assert on the throwable's cause.
*/
fun Assert<Throwable>.cause() = prop("cause", Throwable::cause)
fun Assert<Throwable>.cause() = having("cause", Throwable::cause)

/**
* Returns an assert on the throwable's root cause.
*/
fun Assert<Throwable>.rootCause() = prop("rootCause", Throwable::rootCause)
fun Assert<Throwable>.rootCause() = having("rootCause", Throwable::rootCause)

/**
* Asserts the throwable has the expected message.
Expand Down
42 changes: 21 additions & 21 deletions assertk/src/commonTest/kotlin/test/assertk/assertions/AnyTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -240,66 +240,66 @@ class AnyTest {
assertEquals("expected [int]:<[99]> but was:<[42]> (test)", error.message)
}

//region prop
//region having
@Test
fun prop_passes() {
assertThat(subject).prop("str") { it.str }.isEqualTo("test")
fun having_passes() {
assertThat(subject).having("str") { it.str }.isEqualTo("test")
}

@Test
fun prop_includes_name_in_failure_message() {
fun having_includes_name_in_failure_message() {
val error = assertFailsWith<AssertionError> {
assertThat(subject).prop("str") { it.str }.isEmpty()
assertThat(subject).having("str") { it.str }.isEmpty()
}
assertEquals("expected [str] to be empty but was:<\"test\"> (test)", error.message)
}

@Test
fun nested_prop_include_names_in_failure_message() {
fun nested_having_include_names_in_failure_message() {
val error = assertFailsWith<AssertionError> {
assertThat(subject).prop("other") { it.other }.prop("str") { it?.str }.isNotNull()
assertThat(subject).having("other") { it.other }.having("str") { it?.str }.isNotNull()
}
assertEquals("expected [other.str] to not be null (test)", error.message)
}

@Test
fun prop_property1_extract_prop_passes() {
assertThat(subject).prop(BasicObject::str).isEqualTo("test")
fun having_property1_extract_prop_passes() {
assertThat(subject).having(BasicObject::str).isEqualTo("test")
}

@Test
fun prop_property1_extract_prop_includes_name_in_failure_message() {
fun having_property1_extract_prop_includes_name_in_failure_message() {
val error = assertFailsWith<AssertionError> {
assertThat(subject).prop(BasicObject::str).isEmpty()
assertThat(subject).having(BasicObject::str).isEmpty()
}
assertEquals("expected [str] to be empty but was:<\"test\"> (test)", error.message)
}

@Test
fun prop_property1_includes_error_message_when_fails() {
fun having_property1_includes_error_message_when_fails() {
val error = assertFails {
assertThat(subject).prop(BasicObject::failing).isEmpty()
assertThat(subject).having(BasicObject::failing).isEmpty()
}
assertEquals("sorry!", error.message)
}

@Test
fun prop_callable_function_passes() {
assertThat(subject).prop(BasicObject::funcA).isEqualTo("A")
fun having_callable_function_passes() {
assertThat(subject).having(BasicObject::funcA).isEqualTo("A")
}

@Test
fun prop_callable_function_includes_name_in_failure_message() {
fun having_callable_function_includes_name_in_failure_message() {
val error = assertFailsWith<AssertionError> {
assertThat(subject).prop(BasicObject::funcA).isEqualTo(14)
assertThat(subject).having(BasicObject::funcA).isEqualTo(14)
}
assertEquals("expected [funcA]:<[14]> but was:<[\"A\"]> (test)", error.message)
}

@Test
fun nested_prop_callable_function_include_names_in_failure_message() {
fun nested_having_callable_function_include_names_in_failure_message() {
val error = assertFailsWith<AssertionError> {
assertThat(subject).prop(BasicObject::funcB).prop(BasicObject::funcA).isNull()
assertThat(subject).having(BasicObject::funcB).having(BasicObject::funcA).isNull()
}
assertEquals("expected [funcB.funcA] to be null but was:<\"A\"> (test)", error.message)
}
Expand Down Expand Up @@ -456,7 +456,7 @@ class AnyTest {
@Test
fun isInstanceOf_kclass_run_block_when_passes() {
val error = assertFailsWith<AssertionError> {
assertThat(subject as TestObject).isInstanceOf(BasicObject::class).prop("str", BasicObject::str)
assertThat(subject as TestObject).isInstanceOf(BasicObject::class).having("str", BasicObject::str)
.isEqualTo("wrong")
}
assertEquals("expected [str]:<\"[wrong]\"> but was:<\"[test]\"> (test)", error.message)
Expand All @@ -465,7 +465,7 @@ class AnyTest {
@Test
fun isInstanceOf_reified_kclass_run_block_when_passes() {
val error = assertFailsWith<AssertionError> {
assertThat(subject as TestObject).isInstanceOf<BasicObject>().prop("str", BasicObject::str)
assertThat(subject as TestObject).isInstanceOf<BasicObject>().having("str", BasicObject::str)
.isEqualTo("wrong")
}
assertEquals("expected [str]:<\"[wrong]\"> but was:<\"[test]\"> (test)", error.message)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -630,4 +630,4 @@ class IterableTest {
}

private fun <T> emptyIterable(): Iterable<T> = emptyList()
private fun <T> iterableOf(vararg elements: T): Iterable<T> = elements.toList()
private fun <T> iterableOf(vararg elements: T): Iterable<T> = elements.toList()
Original file line number Diff line number Diff line change
Expand Up @@ -661,4 +661,4 @@ class SequenceTest {
private fun <T> oneshotSequenceOf(vararg elements: T): Sequence<T> {
var i = 0
return generateSequence { elements.getOrNull(i++) }
}
}
6 changes: 3 additions & 3 deletions assertk/src/jvmMain/kotlin/assertk/assertions/any.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import kotlin.reflect.full.memberProperties
/**
* Returns an assert on the java class of the value.
*/
fun <T : Any> Assert<T>.jClass() = prop("class") { it::class.java }
fun <T : Any> Assert<T>.jClass() = having("class") { it::class.java }

/**
* Asserts the value has the expected java class. This is an exact match, so
Expand Down Expand Up @@ -86,7 +86,7 @@ private fun <T> Assert<T>.isDataClassEqualToImpl(expected: T, kclass: KClass<*>?
for (memberProp in kclass.memberProperties) {
@Suppress("UNCHECKED_CAST")
val force = memberProp as KProperty1<T, Any?>
prop(force).isDataClassEqualToImpl(force.get(expected), force.returnType.classifier as? KClass<*>)
having(force).isDataClassEqualToImpl(force.get(expected), force.returnType.classifier as? KClass<*>)
}
} else {
isEqualTo(expected)
Expand Down Expand Up @@ -120,4 +120,4 @@ fun <T : Any> Assert<T>.isEqualToIgnoringGivenProperties(other: T, vararg proper
}
}
}
}
}
Loading

0 comments on commit 6b65668

Please sign in to comment.