Repository: assertj/assertj

Problem statement:
Add hasSuperclass(Class<?>) to AbstractClassAssert
#### Summary
Sometimes I feel the lack of `isNotAssignableFrom()` in `AbstractClassAssert`.
Recently I needed a defensive test to enforce an exception class staying "checked" (i.e., not inheriting from `RuntimeException`). To do that, I created a custom condition:

```java
private static Condition<Class<?>> runtimeException = new Condition<>(RuntimeException.class::isAssignableFrom, RuntimeException.class.getSimpleName());
```
used in this way:
```java
assertThat(MyException.class).isNot(runtimeException);
```

#### Example
Following the behavior of the existing `isAssignableFrom(Class<?>...)`, a new api could check that `actual` is not assignable from any of the `others`:
```java
public SELF isNotAssignableFrom(Class<?>... others)
```
With it, my example would not require a custom condition:
```java
assertThat(RuntimeException.class).isNotAssignableFrom(MyException.class);
```

Would this new api make sense?

#### Additional Thoughts
Would it make sense to have some assertions for `Throwable` classes, for example to verify if the class is inheriting from `Error`/`Exception`/`RuntimeException`? It would address the ugly backwards readability of `isAssignableFrom` :-)
Maybe a new `AbstractThrowableClassAssert` inheriting from `AbstractClassAssert` could to the job.


Hints:
I'm ok with `isNotAssignableFrom` as the opposite of `isAssignableFrom`.

Having said that, I have always found the (JDK) `isAssignableFrom` method to be highly unclear, I often find myself wondering which class should inherit from the other one.

It would be great to find a better name for the `isAssignableFrom` assertion (and `isNotAssignableFrom`) that would not be ambiguous, maybe `isA/isAn` and `isNotA/isNotAn`? 

```java
assertThat(MyException.class).isAn(Error.class);
assertThat(MyException.class).isNotA(RuntimeException.class);
```

I don't see adding `AbstractThrowableClassAssert` to be  valuable enough at this point, were you thinking having assertions like `isRuntimeException()` ?
Yes, I was thinking about something like `isRuntimeException()` just because of the JDK `isAssignableFrom` unclearness! But you're right, there would be no need for an `AbstractThrowableClassAssert` having a better name for `isAssignableFrom`.

I would avoid the `isA`/`isAn` duplication, the options that came to my mind:
1. `isInheritingFrom()` and potentially a `isBeingInheritedFrom()` for the superclass use case
2. `hasSuperclass()` / `hasSubclass()`, but it's probably too close to [Class#getSuperclass()](https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html#getSuperclass--) and it could create confusion since the `getSuperclass()` has less flexibility
3. `isChildOf()`/`isParentOf()` or `hasParent()`/`hasChild()`
@joel-costigliola let's try again on this topic, but this time simpler :-)

What about having `isAssignableTo(Class<?>)`?
@joel-costigliola one more try 🙂 

What about `hasSuperclass(Class<?>)` which is backed by [`getSuperclass()`](https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html#getSuperclass--) from the JDK?

Not flexible as the previous one as it doesn't match implemented interfaces but it's good enough for my purpose and doesn't deviate from JDK api.
Works for me! :)

Patch:
diff --git a/src/main/java/org/assertj/core/api/AbstractClassAssert.java b/src/main/java/org/assertj/core/api/AbstractClassAssert.java
--- a/src/main/java/org/assertj/core/api/AbstractClassAssert.java
+++ b/src/main/java/org/assertj/core/api/AbstractClassAssert.java
@@ -349,6 +349,45 @@ public SELF hasAnnotation(Class<? extends Annotation> annotation) {
     return myself;
   }
 
+  /**
+   * Verifies that the actual {@code Class} has the given class as direct superclass (as in {@link Class#getSuperclass()}).
+   * <p>
+   * Example:
+   * <pre><code class='java'> // this assertion succeeds as Object has no superclass:
+   * assertThat(Object.class).hasSuperclass(null);
+   *
+   * // this assertion succeeds as interfaces have no superclass:
+   * assertThat(Cloneable.class).hasSuperclass(null);
+   *
+   * // this assertion succeeds as primitive types have no superclass:
+   * assertThat(Integer.TYPE).hasSuperclass(null);
+   *
+   * // this assertion succeeds as void type has no superclass:
+   * assertThat(Void.TYPE).hasSuperclass(null);
+   *
+   * // this assertion succeeds:
+   * assertThat(Integer.class).hasSuperclass(Number.class);
+   *
+   * // this assertion succeeds as superclass for array classes is Object:
+   * assertThat(Integer[].class).hasSuperclass(Object.class);
+   *
+   * // this assertion fails as only direct superclass matches:
+   * assertThat(Integer.class).hasSuperclass(Object.class);
+   *
+   * // this assertion fails as interfaces are not superclasses:
+   * assertThat(Integer.class).hasSuperclass(Comparable.class);</code></pre>
+   *
+   * @param superclass the class which must be the direct superclass of actual.
+   * @return {@code this} assertions object
+   * @throws AssertionError if {@code actual} is {@code null}.
+   * @throws AssertionError if the actual {@code Class} doesn't have the given class as direct superclass.
+   * @since 3.15.0
+   */
+  public SELF hasSuperclass(Class<?> superclass) {
+    classes.assertHasSuperclass(info, actual, superclass);
+    return myself;
+  }
+
   /**
    * @deprecated use {@link #hasPublicFields(String...)} instead.
    * @param fields the fields who must be in the class.
diff --git a/src/main/java/org/assertj/core/error/ShouldHaveAnnotations.java b/src/main/java/org/assertj/core/error/ShouldHaveAnnotations.java
--- a/src/main/java/org/assertj/core/error/ShouldHaveAnnotations.java
+++ b/src/main/java/org/assertj/core/error/ShouldHaveAnnotations.java
@@ -16,7 +16,7 @@
 import java.util.Collection;
 
 /**
- * Creates an error message indicating that an assertion that verifies that a class have an annotation.
+ * Creates an error message indicating that an assertion that verifies that a class has annotations failed.
  * 
  * @author William Delanoue
  * @author Joel Costigliola
@@ -24,7 +24,7 @@
 public class ShouldHaveAnnotations extends BasicErrorMessageFactory {
 
   /**
-   * Creates a new <code>{@link org.assertj.core.error.ShouldHaveAnnotations}</code>.
+   * Creates a new <code>{@link ShouldHaveAnnotations}</code>.
    * 
    * @param actual the actual value in the failed assertion.
    * @param expected expected annotations for this class
diff --git a/src/main/java/org/assertj/core/error/ShouldHaveSuperclass.java b/src/main/java/org/assertj/core/error/ShouldHaveSuperclass.java
new file mode 100644
--- /dev/null
+++ b/src/main/java/org/assertj/core/error/ShouldHaveSuperclass.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * Copyright 2012-2019 the original author or authors.
+ */
+package org.assertj.core.error;
+
+import java.util.StringJoiner;
+
+/**
+ * Creates an error message indicating that an assertion that verifies that a class has a given superclass failed.
+ * 
+ * @author Stefano Cordio
+ */
+public class ShouldHaveSuperclass extends BasicErrorMessageFactory {
+
+  private static final String SHOULD_HAVE_SUPERCLASS = new StringJoiner("%n", "%n", "").add("Expecting")
+                                                                                       .add("  <%s>")
+                                                                                       .add("to have superclass:")
+                                                                                       .add("  <%s>")
+                                                                                       .add("but had:")
+                                                                                       .add("  <%s>")
+                                                                                       .toString();
+
+  /**
+   * Creates a new <code>{@link ShouldHaveSuperclass}</code>.
+   *
+   * @param actual     the actual value in the failed assertion.
+   * @param superclass expected superclass for this class.
+   * @return the created {@code ErrorMessageFactory}.
+   */
+  public static ErrorMessageFactory shouldHaveSuperclass(Class<?> actual, Class<?> superclass) {
+    return new ShouldHaveSuperclass(actual, superclass);
+  }
+
+  private ShouldHaveSuperclass(Class<?> actual, Class<?> superclass) {
+    super(SHOULD_HAVE_SUPERCLASS, actual, superclass, actual.getSuperclass());
+  }
+
+}
diff --git a/src/main/java/org/assertj/core/internal/Classes.java b/src/main/java/org/assertj/core/internal/Classes.java
--- a/src/main/java/org/assertj/core/internal/Classes.java
+++ b/src/main/java/org/assertj/core/internal/Classes.java
@@ -31,6 +31,7 @@
 import static org.assertj.core.error.ShouldHaveMethods.shouldNotHaveMethods;
 import static org.assertj.core.error.ShouldHaveNoFields.shouldHaveNoDeclaredFields;
 import static org.assertj.core.error.ShouldHaveNoFields.shouldHaveNoPublicFields;
+import static org.assertj.core.error.ShouldHaveSuperclass.shouldHaveSuperclass;
 import static org.assertj.core.error.ShouldOnlyHaveFields.shouldOnlyHaveDeclaredFields;
 import static org.assertj.core.error.ShouldOnlyHaveFields.shouldOnlyHaveFields;
 import static org.assertj.core.util.Lists.newArrayList;
@@ -258,6 +259,23 @@ public void assertContainsAnnotations(AssertionInfo info, Class<?> actual,
     if (!missing.isEmpty()) throw failures.failure(info, shouldHaveAnnotations(actual, expected, missing));
   }
 
+  /**
+   * Verifies that the actual {@code Class} has the given class as direct {@code superclass}.
+   *
+   * @param info       contains information about the assertion.
+   * @param actual     the "actual" {@code Class}.
+   * @param superclass the direct superclass, which can be null according to {@link Class#getSuperclass()}.
+   * @throws AssertionError if {@code actual} is {@code null}.
+   * @throws AssertionError if the actual {@code Class} superclass does not have the expected superclass.
+   */
+  public void assertHasSuperclass(AssertionInfo info, Class<?> actual, Class<?> superclass) {
+    assertNotNull(info, actual);
+    Class<?> actualSuperclass = actual.getSuperclass();
+    if ((actualSuperclass == null && superclass != null) || (actualSuperclass != null && !actualSuperclass.equals(superclass))) {
+      throw failures.failure(info, shouldHaveSuperclass(actual, superclass));
+    }
+  }
+
   /**
    * Verifies that the actual {@code Class} has the {@code fields}.
    * 


Test patch:
diff --git a/src/test/java/org/assertj/core/api/classes/ClassAssert_hasSuperclass_Test.java b/src/test/java/org/assertj/core/api/classes/ClassAssert_hasSuperclass_Test.java
new file mode 100644
--- /dev/null
+++ b/src/test/java/org/assertj/core/api/classes/ClassAssert_hasSuperclass_Test.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * Copyright 2012-2019 the original author or authors.
+ */
+package org.assertj.core.api.classes;
+
+import static org.mockito.Mockito.verify;
+
+import org.assertj.core.api.ClassAssert;
+import org.assertj.core.api.ClassAssertBaseTest;
+import org.junit.jupiter.api.DisplayName;
+
+/**
+ * Tests for <code>{@link ClassAssert#hasSuperclass(Class)}</code>.
+ * 
+ * @author Stefano Cordio
+ */
+@DisplayName("ClassAssert hasSuperclass")
+class ClassAssert_hasSuperclass_Test extends ClassAssertBaseTest {
+
+  @Override
+  protected ClassAssert invoke_api_method() {
+    return assertions.hasSuperclass(Object.class);
+  }
+
+  @Override
+  protected void verify_internal_effects() {
+    verify(classes).assertHasSuperclass(getInfo(assertions), getActual(assertions), Object.class);
+  }
+
+}
diff --git a/src/test/java/org/assertj/core/error/ShouldHaveSuperclass_create_Test.java b/src/test/java/org/assertj/core/error/ShouldHaveSuperclass_create_Test.java
new file mode 100644
--- /dev/null
+++ b/src/test/java/org/assertj/core/error/ShouldHaveSuperclass_create_Test.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * Copyright 2012-2019 the original author or authors.
+ */
+package org.assertj.core.error;
+
+import static java.lang.String.format;
+import static org.assertj.core.api.BDDAssertions.then;
+import static org.assertj.core.error.ShouldHaveSuperclass.shouldHaveSuperclass;
+import static org.assertj.core.presentation.StandardRepresentation.STANDARD_REPRESENTATION;
+
+import org.assertj.core.description.Description;
+import org.assertj.core.internal.TestDescription;
+import org.assertj.core.presentation.Representation;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for <code>{@link ShouldHaveSuperclass#create(Description, Representation)}</code>.
+ *
+ * @author Stefano Cordio
+ */
+@DisplayName("ShouldHaveSuperclass create")
+class ShouldHaveSuperclass_create_Test {
+
+  @Test
+  void should_create_error_message() {
+    // WHEN
+    String message = shouldHaveSuperclass(String.class, Integer.class).create(new TestDescription("TEST"),
+                                                                              STANDARD_REPRESENTATION);
+    // THEN
+    then(message).isEqualTo(format("[TEST] %n" +
+                                   "Expecting%n" +
+                                   "  <java.lang.String>%n" +
+                                   "to have superclass:%n" +
+                                   "  <java.lang.Integer>%n" +
+                                   "but had:%n" +
+                                   "  <java.lang.Object>"));
+  }
+
+}
diff --git a/src/test/java/org/assertj/core/internal/classes/Classes_assertHasSuperclass_Test.java b/src/test/java/org/assertj/core/internal/classes/Classes_assertHasSuperclass_Test.java
new file mode 100644
--- /dev/null
+++ b/src/test/java/org/assertj/core/internal/classes/Classes_assertHasSuperclass_Test.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * Copyright 2012-2019 the original author or authors.
+ */
+package org.assertj.core.internal.classes;
+
+import static org.assertj.core.api.BDDAssertions.then;
+import static org.assertj.core.error.ShouldHaveSuperclass.shouldHaveSuperclass;
+import static org.assertj.core.test.TestData.someInfo;
+import static org.assertj.core.util.AssertionsUtil.expectAssertionError;
+import static org.assertj.core.util.FailureMessages.actualIsNull;
+
+import java.util.stream.Stream;
+
+import org.assertj.core.internal.ClassesBaseTest;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.NullSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+@DisplayName("Classes assertHasSuperclass")
+class Classes_assertHasSuperclass_Test extends ClassesBaseTest {
+
+  @Test
+  void should_fail_if_actual_is_null() {
+    // GIVEN
+    Class<?> actual = null;
+    // WHEN
+    AssertionError assertionError = expectAssertionError(() -> classes.assertHasSuperclass(someInfo(), actual, Object.class));
+    // THEN
+    then(assertionError).hasMessage(actualIsNull());
+  }
+
+  @Test
+  void should_pass_if_actual_has_given_class_as_direct_superclass() {
+    // GIVEN
+    Class<?> actual = Integer.class;
+    Class<?> superclass = Number.class;
+    // WHEN/THEN
+    classes.assertHasSuperclass(someInfo(), actual, superclass);
+  }
+
+  @ParameterizedTest
+  @NullSource
+  @ValueSource(classes = { Object.class, Comparable.class, String.class })
+  void should_fail_if_actual_has_not_given_class_as_direct_superclass(Class<?> superclass) {
+    // GIVEN
+    Class<?> actual = Integer.class;
+    // WHEN
+    AssertionError assertionError = expectAssertionError(() -> classes.assertHasSuperclass(someInfo(), actual, superclass));
+    // THEN
+    then(assertionError).hasMessage(shouldHaveSuperclass(actual, superclass).create());
+  }
+
+  @Test
+  void should_pass_if_actual_is_an_array_class_and_object_class_is_given() {
+    // GIVEN
+    Class<?> actual = Integer[].class;
+    // WHEN/THEN
+    classes.assertHasSuperclass(someInfo(), actual, Object.class);
+  }
+
+  @ParameterizedTest
+  @MethodSource("nullSuperclassTypes")
+  void should_pass_if_actual_has_no_superclass_and_null_is_given(Class<?> actual) {
+    // WHEN/THEN
+    classes.assertHasSuperclass(someInfo(), actual, null);
+  }
+
+  private static Stream<Class<?>> nullSuperclassTypes() {
+    return Stream.of(Object.class,
+                     Cloneable.class, // any interface
+                     Boolean.TYPE,
+                     Byte.TYPE,
+                     Character.TYPE,
+                     Double.TYPE,
+                     Float.TYPE,
+                     Integer.TYPE,
+                     Long.TYPE,
+                     Short.TYPE,
+                     Void.TYPE);
+  }
+
+}
diff --git a/src/test/java/org/assertj/core/internal/classes/Classes_assertIsPackagePrivate_Test.java b/src/test/java/org/assertj/core/internal/classes/Classes_assertIsPackagePrivate_Test.java
--- a/src/test/java/org/assertj/core/internal/classes/Classes_assertIsPackagePrivate_Test.java
+++ b/src/test/java/org/assertj/core/internal/classes/Classes_assertIsPackagePrivate_Test.java
@@ -50,7 +50,7 @@ void should_fail_if_actual_is_not_a_package_private_class() {
     // WHEN
     AssertionError assertionError = expectAssertionError(() -> classes.assertIsPackagePrivate(someInfo(), clazz));
     // THEN
-    then(assertionError).hasMessage(shouldBePackagePrivate(Object.class).create());
+    then(assertionError).hasMessage(shouldBePackagePrivate(clazz).create());
   }
 
   static class PackagePrivateClass {