Repository: willowtreeapps/assertk (kotlin)

Issue:
New assertion overload: inline reified `isInstanceOf` / `isNotInstanceOf`
I'm really not one for code golf or anything, but I was surprised at its omission. My justification is that `isInstanceOf(KClass<T>)` provides the base functionality, and by the nature of `reified` requiring `inline` an `inline fun <reified T : Any> Assert<Any>.isInstanceOf(): Assert<T>` helper provides language feature support on top.

It also potentially allows inference to be used so as to not require explicitly specifying the type at the function call at all?

Pull request description:
Closes #454

Patch:
diff --git a/assertk/src/commonMain/kotlin/assertk/assertions/any.kt b/assertk/src/commonMain/kotlin/assertk/assertions/any.kt
index 33d85971..a74cf792 100644
--- a/assertk/src/commonMain/kotlin/assertk/assertions/any.kt
+++ b/assertk/src/commonMain/kotlin/assertk/assertions/any.kt
@@ -164,6 +164,14 @@ fun <T, P> Assert<T>.prop(property: KProperty1<T, P>): Assert<P> =
 fun <T, R, F> Assert<T>.prop(callable: F): Assert<R> where F : (T) -> R, F : KCallable<R> =
     prop(callable.name, callable)
 
+/**
+ * Asserts the value has the expected kotlin class. This is an exact match, so `assertThat("test").hasClass<String>()`
+ * is successful but `assertThat("test").hasClass<Any>()` fails.
+ * @see [doesNotHaveClass]
+ * @see [isInstanceOf]
+ */
+inline fun <reified T : Any> Assert<Any>.hasClass() = hasClass(T::class)
+
 /**
  * Asserts the value has the expected kotlin class. This is an exact match, so `assertThat("test").hasClass(String::class)`
  * is successful but `assertThat("test").hasClass(Any::class)` fails.
@@ -177,7 +185,16 @@ fun <T : Any> Assert<T>.hasClass(kclass: KClass<out T>) = given { actual ->
 
 /**
  * Asserts the value does not have the expected kotlin class. This is an exact match, so
- * `assertThat("test").doesNotHaveClass(String::class)` is fails but `assertThat("test").doesNotHaveClass(Any::class)` is
+ * `assertThat("test").doesNotHaveClass<String>()` fails but `assertThat("test").doesNotHaveClass<Any>()` is
+ * successful.
+ * @see [hasClass]
+ * @see [isNotInstanceOf]
+ */
+inline fun <reified T : Any> Assert<Any>.doesNotHaveClass() = doesNotHaveClass(T::class)
+
+/**
+ * Asserts the value does not have the expected kotlin class. This is an exact match, so
+ * `assertThat("test").doesNotHaveClass(String::class)` fails but `assertThat("test").doesNotHaveClass(Any::class)` is
  * successful.
  * @see [hasClass]
  * @see [isNotInstanceOf]
@@ -186,10 +203,17 @@ 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.
+ * @see [isInstanceOf]
+ * @see [doesNotHaveClass]
+ */
+inline fun <reified T : Any> Assert<Any>.isNotInstanceOf() = isNotInstanceOf(T::class)
 
 /**
  * Asserts the value is not an instance of the expected kotlin class. Both
- * `assertThat("test").isNotInstanceOf(String::class)` and `assertThat("test").isNotInstanceOf(Any::class)` fails.
+ * `assertThat("test").isNotInstanceOf(String::class)` and `assertThat("test").isNotInstanceOf(Any::class)` fail.
  * @see [isInstanceOf]
  * @see [doesNotHaveClass]
  */
@@ -198,9 +222,17 @@ fun <T : Any> Assert<T>.isNotInstanceOf(kclass: KClass<out T>) = given { actual
     expected("to not be instance of:${show(kclass)}")
 }
 
+/**
+ * Asserts the value is an instance of the expected kotlin class. Both `assertThat("test").isInstanceOf<String>()` and
+ * `assertThat("test").isInstanceOf<Any>()` are successful.
+ * @see [isNotInstanceOf]
+ * @see [hasClass]
+ */
+inline fun <reified T : Any> Assert<Any>.isInstanceOf() = isInstanceOf(T::class)
+
 /**
  * Asserts the value is an instance of the expected kotlin class. Both `assertThat("test").isInstanceOf(String::class)` and
- * `assertThat("test").isInstanceOf(Any::class)` is successful.
+ * `assertThat("test").isInstanceOf(Any::class)` are successful.
  * @see [isNotInstanceOf]
  * @see [hasClass]
  */


Test patch:
diff --git a/assertk/src/commonTest/kotlin/test/assertk/assertions/AnyTest.kt b/assertk/src/commonTest/kotlin/test/assertk/assertions/AnyTest.kt
index 7883652a..b08eebd8 100644
--- a/assertk/src/commonTest/kotlin/test/assertk/assertions/AnyTest.kt
+++ b/assertk/src/commonTest/kotlin/test/assertk/assertions/AnyTest.kt
@@ -292,6 +292,10 @@ class AnyTest {
         assertThat(subject).hasClass(BasicObject::class)
     }
 
+    @Test fun hasClass_reified_same_class_passes() {
+        assertThat(subject).hasClass<BasicObject>()
+    }
+
     @Test fun hasClass_parent_class_fails() {
         val error = assertFailsWith<AssertionError> {
             assertThat(subject).hasClass(TestObject::class)
@@ -302,10 +306,24 @@ class AnyTest {
         )
     }
 
+    @Test fun hasClass_reified_parent_class_fails() {
+        val error = assertFailsWith<AssertionError> {
+            assertThat(subject).hasClass<TestObject>()
+        }
+        assertEquals(
+            "expected to have class:<${TestObject::class}> but was:<${BasicObject::class}>",
+            error.message
+        )
+    }
+
     @Test fun doesNotHaveClass_parent_class_passes() {
         assertThat(subject).doesNotHaveClass(TestObject::class)
     }
 
+    @Test fun doesNotHaveClass_reified_parent_class_passes() {
+        assertThat(subject).doesNotHaveClass<TestObject>()
+    }
+
     @Test fun doesNotHaveClass_same_class_fails() {
         val error = assertFailsWith<AssertionError> {
             assertThat(subject).doesNotHaveClass(BasicObject::class)
@@ -313,14 +331,29 @@ class AnyTest {
         assertEquals("expected to not have class:<${BasicObject::class}>", error.message)
     }
 
+    @Test fun doesNotHaveClass_reified_same_class_fails() {
+        val error = assertFailsWith<AssertionError> {
+            assertThat(subject).doesNotHaveClass<BasicObject>()
+        }
+        assertEquals("expected to not have class:<${BasicObject::class}>", error.message)
+    }
+
     @Test fun isInstanceOf_kclass_same_class_passes() {
         assertThat(subject as TestObject).isInstanceOf(BasicObject::class)
     }
 
+    @Test fun isInstanceOf_reified_kclass_same_class_passes() {
+        assertThat(subject as TestObject).isInstanceOf<BasicObject>()
+    }
+
     @Test fun isInstanceOf_kclass_parent_class_passes() {
         assertThat(subject).isInstanceOf(TestObject::class)
     }
 
+    @Test fun isInstanceOf_reified_kclass_parent_class_passes() {
+        assertThat(subject).isInstanceOf<TestObject>()
+    }
+
     @Test fun isInstanceOf_kclass_different_class_fails() {
         val error = assertFailsWith<AssertionError> {
             assertThat(subject).isInstanceOf(DifferentObject::class)
@@ -331,6 +364,16 @@ class AnyTest {
         )
     }
 
+    @Test fun isInstanceOf_reified_kclass_different_class_fails() {
+        val error = assertFailsWith<AssertionError> {
+            assertThat(subject).isInstanceOf<DifferentObject>()
+        }
+        assertEquals(
+            "expected to be instance of:<${DifferentObject::class}> but had class:<${BasicObject::class}>",
+            error.message
+        )
+    }
+
     @Test fun isInstanceOf_kclass_run_block_when_passes() {
         val error = assertFailsWith<AssertionError> {
             assertThat(subject as TestObject).isInstanceOf(BasicObject::class).prop("str", BasicObject::str)
@@ -339,6 +382,14 @@ class AnyTest {
         assertEquals("expected [str]:<\"[wrong]\"> but was:<\"[test]\"> (test)", error.message)
     }
 
+    @Test fun isInstanceOf_reified_kclass_run_block_when_passes() {
+        val error = assertFailsWith<AssertionError> {
+            assertThat(subject as TestObject).isInstanceOf<BasicObject>().prop("str", BasicObject::str)
+                .isEqualTo("wrong")
+        }
+        assertEquals("expected [str]:<\"[wrong]\"> but was:<\"[test]\"> (test)", error.message)
+    }
+
     @Test fun isInstanceOf_kclass_doesnt_run_block_when_fails() {
         val error = assertFailsWith<AssertionError> {
             assertThat(subject as TestObject).isInstanceOf(DifferentObject::class).isNull()
@@ -349,10 +400,24 @@ class AnyTest {
         )
     }
 
+    @Test fun isInstanceOf_reified_kclass_doesnt_run_block_when_fails() {
+        val error = assertFailsWith<AssertionError> {
+            assertThat(subject as TestObject).isInstanceOf<DifferentObject>().isNull()
+        }
+        assertEquals(
+            "expected to be instance of:<${DifferentObject::class}> but had class:<${BasicObject::class}>",
+            error.message
+        )
+    }
+
     @Test fun isNotInstanceOf_kclass_different_class_passes() {
         assertThat(subject).isNotInstanceOf(DifferentObject::class)
     }
 
+    @Test fun isNotInstanceOf_reified_kclass_different_class_passes() {
+        assertThat(subject).isNotInstanceOf<DifferentObject>()
+    }
+
     @Test fun isNotInstanceOf_kclass_same_class_fails() {
         val error = assertFailsWith<AssertionError> {
             assertThat(subject).isNotInstanceOf(BasicObject::class)
@@ -360,6 +425,13 @@ class AnyTest {
         assertEquals("expected to not be instance of:<${BasicObject::class}>", error.message)
     }
 
+    @Test fun isNotInstanceOf_reified_kclass_same_class_fails() {
+        val error = assertFailsWith<AssertionError> {
+            assertThat(subject).isNotInstanceOf<BasicObject>()
+        }
+        assertEquals("expected to not be instance of:<${BasicObject::class}>", error.message)
+    }
+
     @Test fun isNotInstanceOf_kclass_parent_class_fails() {
         val error = assertFailsWith<AssertionError> {
             assertThat(subject).isNotInstanceOf(TestObject::class)
@@ -367,6 +439,13 @@ class AnyTest {
         assertEquals("expected to not be instance of:<${TestObject::class}>", error.message)
     }
 
+    @Test fun isNotInstanceOf_reifeid_kclass_parent_class_fails() {
+        val error = assertFailsWith<AssertionError> {
+            assertThat(subject).isNotInstanceOf<TestObject>()
+        }
+        assertEquals("expected to not be instance of:<${TestObject::class}>", error.message)
+    }
+
     @Test fun corresponds_equivalent_passes() {
         assertThat(subject).corresponds(BasicObject("different")) { _, _ -> true }
     }