Skip to content

Commit

Permalink
Add hasPermittedSubclasses and hasNoPermittedSubclasses for Class ass…
Browse files Browse the repository at this point in the history
…ertions - Fix #3263

Co-authored-by: Stefano Cordio <stefano_cordio@epam.com>
  • Loading branch information
2 people authored and joel-costigliola committed May 14, 2024
1 parent 2dac678 commit a0cb89b
Show file tree
Hide file tree
Showing 8 changed files with 292 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import static org.assertj.core.error.ShouldHaveNoPackage.shouldHaveNoPackage;
import static org.assertj.core.error.ShouldHaveNoSuperclass.shouldHaveNoSuperclass;
import static org.assertj.core.error.ShouldHavePackage.shouldHavePackage;
import static org.assertj.core.error.ShouldHavePermittedSubclasses.shouldHavePermittedSubclasses;
import static org.assertj.core.error.ShouldHaveRecordComponents.shouldHaveRecordComponents;
import static org.assertj.core.error.ShouldHaveSuperclass.shouldHaveSuperclass;
import static org.assertj.core.error.ShouldNotBeNull.shouldNotBeNull;
Expand All @@ -47,6 +48,8 @@
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.assertj.core.internal.Classes;

Expand Down Expand Up @@ -1198,6 +1201,59 @@ private static boolean isSealed(Class<?> actual) {
}
}

/**
* Verifies that the actual {@code Class} permitted subclasses contains the given classes.
* <p>
* Example:
* <pre><code class='java'> sealed class SealedClass permits NonSealedClass, FinalClass {}
* non-sealed class NonSealedClass extends SealedClass {}
* final class FinalClass extends SealedClass {}
*
* // these assertions succeed:
* assertThat(SealedClass.class).hasPermittedSubclasses(NonSealedClass.class)
* .hasPermittedSubclasses(FinalClass.class)
* .hasPermittedSubclasses(NonSealedClass.class, FinalClass.class)
* .hasPermittedSubclasses();
*
* // these assertions fail:
* assertThat(SealedClass.class).hasPermittedSubclasses(String.class);
* assertThat(SealedClass.class).hasPermittedSubclasses(FinalClass.class, String.class);</code></pre>
*
* @param permittedSubclasses classes that must be permitted subclasses of the given class
* @return {@code this} assertions object
* @throws AssertionError if {@code actual} is {@code null}.
* @throws AssertionError if the actual {@code Class} does not have all of given permitted subclasses
*/
public SELF hasPermittedSubclasses(Class<?>... permittedSubclasses) {
isNotNull();
assertHasPermittedSubclasses(permittedSubclasses);
return myself;
}

private void assertHasPermittedSubclasses(Class<?>[] expectedPermittedSubclasses) {
for (Class<?> expectedPermittedSubclass : expectedPermittedSubclasses) {
classes.classParameterIsNotNull(expectedPermittedSubclass);
}
Set<Class<?>> actualPermittedSubclasses = newLinkedHashSet(getPermittedSubclasses(actual));
Set<Class<?>> missingPermittedSubclasses = Stream.of(expectedPermittedSubclasses)
.filter(expectedPermittedSubclass -> !actualPermittedSubclasses.contains(expectedPermittedSubclass))
.collect(Collectors.toSet());
if (!missingPermittedSubclasses.isEmpty())
throw assertionError(shouldHavePermittedSubclasses(actual, expectedPermittedSubclasses, missingPermittedSubclasses));
}

private static Class<?>[] getPermittedSubclasses(Class<?> actual) {
try {
Method getPermittedSubclasses = Class.class.getMethod("getPermittedSubclasses");
Class<?>[] permittedSubclasses = (Class<?>[]) getPermittedSubclasses.invoke(actual);
return permittedSubclasses == null ? array() : permittedSubclasses;
} catch (NoSuchMethodException e) {
return new Class<?>[0];
} catch (ReflectiveOperationException e) {
throw new IllegalStateException(e);
}
}

/**
* Verifies that the actual {@code Class} is a primitive type.
* <p>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* 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-2024 the original author or authors.
*/
package org.assertj.core.error;

import java.util.Collection;

public class ShouldHavePermittedSubclasses extends BasicErrorMessageFactory {

public static ErrorMessageFactory shouldHavePermittedSubclasses(Class<?> actual,
Class<?>[] permittedSubclasses,
Collection<Class<?>> missingSubclasses) {
return new ShouldHavePermittedSubclasses(actual, permittedSubclasses, missingSubclasses);
}

private ShouldHavePermittedSubclasses(Class<?> actual, Class<?>[] permittedSubclasses, Collection<Class<?>> missingSubclasses) {
super("%n" +
"Expecting%n" +
" %s%n" +
"to have these permitted subclasses:%n" +
" %s%n" +
"but the following ones were not found:%n" +
" %s",
actual, permittedSubclasses, missingSubclasses);
}
}
22 changes: 11 additions & 11 deletions assertj-core/src/main/java/org/assertj/core/internal/Classes.java
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,17 @@ public void assertHasPublicMethods(AssertionInfo info, Class<?> actual, String..
}
}

/**
* used to check that the class to compare is not null, in that case throws a {@link NullPointerException} with an
* explicit message.
*
* @param clazz the class to check
* @throws NullPointerException with an explicit message if the given class is null
*/
public void classParameterIsNotNull(Class<?> clazz) {
requireNonNull(clazz, "The class to compare actual with should not be null");
}

private static SortedSet<String> getMethodsWithModifier(Set<Method> methods, int modifier) {
SortedSet<String> methodsWithModifier = newTreeSet();
for (Method method : methods) {
Expand Down Expand Up @@ -389,15 +400,4 @@ private static <M extends Member> Set<M> filterSyntheticMembers(M[] members) {
private static void assertNotNull(AssertionInfo info, Class<?> actual) {
Objects.instance().assertNotNull(info, actual);
}

/**
* used to check that the class to compare is not null, in that case throws a {@link NullPointerException} with an
* explicit message.
*
* @param clazz the date to check
* @throws NullPointerException with an explicit message if the given class is null
*/
private static void classParameterIsNotNull(Class<?> clazz) {
requireNonNull(clazz, "The class to compare actual with should not be null");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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-2024 the original author or authors.
*/
package org.assertj.core.api.classes;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.BDDAssertions.then;
import static org.assertj.core.error.ShouldHavePermittedSubclasses.shouldHavePermittedSubclasses;
import static org.assertj.core.error.ShouldNotBeNull.shouldNotBeNull;
import static org.assertj.core.util.Arrays.array;
import static org.assertj.core.util.AssertionsUtil.expectAssertionError;
import static org.assertj.core.util.Lists.list;

import org.junit.jupiter.api.Test;

class ClassAssert_hasPermittedSubclasses_Test {

@Test
void should_fail_if_actual_is_null() {
// GIVEN
Class<?> actual = null;
// WHEN
AssertionError error = expectAssertionError(() -> assertThat(actual).hasPermittedSubclasses());
// THEN
then(error).hasMessage(shouldNotBeNull().create());
}

@Test
void should_fail_if_actual_does_not_have_permitted_subclasses() {
// WHEN
AssertionError error = expectAssertionError(() -> assertThat(Object.class).hasPermittedSubclasses(String.class));
// THEN
then(error).hasMessage(shouldHavePermittedSubclasses(Object.class, array(String.class), list(String.class)).create());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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-2024 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.ShouldHavePermittedSubclasses.shouldHavePermittedSubclasses;
import static org.assertj.core.util.Arrays.array;
import static org.assertj.core.util.Lists.list;

import org.assertj.core.description.TextDescription;
import org.assertj.core.presentation.StandardRepresentation;
import org.junit.jupiter.api.Test;

class ShouldHavePermittedSubclasses_create_Test {

@Test
void should_create_error_message() {
// GIVEN
ErrorMessageFactory factory = shouldHavePermittedSubclasses(ShouldHavePermittedSubclasses_create_Test.class,
array(String.class, Number.class),
list(Number.class));
// WHEN
String message = factory.create(new TextDescription("Test"), new StandardRepresentation());
// THEN
then(message).isEqualTo(format("[Test] %n" +
"Expecting%n" +
" org.assertj.core.error.ShouldHavePermittedSubclasses_create_Test%n" +
"to have these permitted subclasses:%n" +
" [java.lang.String, java.lang.Number]%n" +
"but the following ones were not found:%n" +
" [java.lang.Number]"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* 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-2024 the original author or authors.
*/
package org.assertj.core.tests.java17;

import org.assertj.core.api.ThrowableAssert;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowableOfType;

public class AssertionUtil {
public static AssertionError expectAssertionError(ThrowableAssert.ThrowingCallable shouldRaiseAssertionError) {
AssertionError error = catchThrowableOfType(shouldRaiseAssertionError, AssertionError.class);
assertThat(error).as("The code under test should have raised an AssertionError").isNotNull();
return error;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@
package org.assertj.core.tests.java17;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable;
import static org.assertj.core.api.BDDAssertions.then;
import static org.assertj.core.error.ShouldBeRecord.shouldNotBeRecord;
import static org.assertj.core.error.ShouldBeSealed.shouldBeSealed;
import static org.assertj.core.error.ShouldBeSealed.shouldNotBeSealed;
import static org.assertj.core.error.ShouldHaveRecordComponents.shouldHaveRecordComponents;
import static org.assertj.core.tests.java17.AssertionUtil.expectAssertionError;
import static org.assertj.core.util.Sets.set;

import org.junit.jupiter.api.Nested;
Expand All @@ -34,40 +34,35 @@ class with_record {

@Test
void hasRecordComponents_should_pass_if_record_has_expected_component() {
// WHEN/THEN
assertThat(MyRecord.class).hasRecordComponents("componentOne");
}

@Test
void hasRecordComponents_should_pass_if_record_has_expected_components() {
// WHEN/THEN
assertThat(MyRecord.class).hasRecordComponents("componentOne", "componentTwo");
}

@Test
void hasRecordComponents_should_fail_if_record_components_are_missing() {
// WHEN
Throwable thrown = catchThrowable(() -> assertThat(MyRecord.class).hasRecordComponents("componentOne",
"missing"));
AssertionError assertionError = expectAssertionError(() -> assertThat(MyRecord.class).hasRecordComponents("componentOne",
"missing"));
// THEN
then(thrown).isInstanceOf(AssertionError.class)
.hasMessage(shouldHaveRecordComponents(MyRecord.class,
set("componentOne", "missing"),
set("missing")).create());
then(assertionError).hasMessage(shouldHaveRecordComponents(MyRecord.class,
set("componentOne", "missing"),
set("missing")).create());
}

@Test
void isNotRecord_should_fail_if_actual_is_a_record() {
// WHEN
Throwable thrown = catchThrowable(() -> assertThat(MyRecord.class).isNotRecord());
AssertionError assertionError = expectAssertionError(() -> assertThat(MyRecord.class).isNotRecord());
// THEN
then(thrown).isInstanceOf(AssertionError.class)
.hasMessage(shouldNotBeRecord(MyRecord.class).create());
then(assertionError).hasMessage(shouldNotBeRecord(MyRecord.class).create());
}

@Test
void isRecord_should_pass_if_actual_is_a_record() {
// WHEN/THEN
assertThat(MyRecord.class).isRecord();
}

Expand All @@ -82,24 +77,21 @@ class with_sealed_class {
@Test
void isNotSealed_should_fail_if_actual_is_sealed() {
// WHEN
Throwable thrown = catchThrowable(() -> assertThat(SealedClass.class).isNotSealed());
AssertionError assertionError = expectAssertionError(() -> assertThat(SealedClass.class).isNotSealed());
// THEN
then(thrown).isInstanceOf(AssertionError.class)
.hasMessage(shouldNotBeSealed(SealedClass.class).create());
then(assertionError).hasMessage(shouldNotBeSealed(SealedClass.class).create());
}

@Test
void isSealed_should_fail_if_actual_is_not_sealed() {
// WHEN
Throwable thrown = catchThrowable(() -> assertThat(NonSealedClass.class).isSealed());
AssertionError assertionError = expectAssertionError(() -> assertThat(NonSealedClass.class).isSealed());
// THEN
then(thrown).isInstanceOf(AssertionError.class)
.hasMessage(shouldBeSealed(NonSealedClass.class).create());
then(assertionError).hasMessage(shouldBeSealed(NonSealedClass.class).create());
}

@Test
void isSealed_should_pass_if_actual_is_sealed() {
// WHEN/THEN
assertThat(SealedClass.class).isSealed();
}

Expand All @@ -108,7 +100,6 @@ private sealed class SealedClass permits NonSealedClass {

private non-sealed class NonSealedClass extends SealedClass {
}

}

}

0 comments on commit a0cb89b

Please sign in to comment.