
com.github.robtimus.junit.support.collections.CollectionTests Maven / Gradle / Ivy
/*
* CollectionTests.java
* Copyright 2020 Rob Spoor
*
* 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.
*/
package com.github.robtimus.junit.support.collections;
import static com.github.robtimus.junit.support.collections.CollectionAssertions.assertHasElements;
import static com.github.robtimus.junit.support.collections.CollectionUtils.commonType;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.params.provider.Arguments.arguments;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.ArgumentsProvider;
import org.junit.jupiter.params.provider.ArgumentsSource;
import com.github.robtimus.junit.support.collections.annotation.ContainsIncompatibleNotSupported;
import com.github.robtimus.junit.support.collections.annotation.ContainsNullNotSupported;
import com.github.robtimus.junit.support.collections.annotation.RemoveIncompatibleNotSupported;
import com.github.robtimus.junit.support.collections.annotation.RemoveNullNotSupported;
/**
* Base interface for testing separate {@link Collection} functionalities.
*
* @author Rob Spoor
* @param The element type of the collection to test.
*/
public interface CollectionTests extends IterableTests {
@Override
Collection iterable();
/**
* Returns some elements that should not be contained by the collection to test.
*
* @return A collection with elements that should not be contained by the collection to test.
*/
Collection nonContainedElements();
/**
* Contains tests for {@link Collection#contains(Object)}.
*
* By default, the tests in this interface assume that calling {@link Collection#contains(Object)} with {@code null} or an instance of an
* incompatible type will simply return {@code false}. If either is not the case, annotate your class with {@link ContainsNullNotSupported} and/or
* {@link ContainsIncompatibleNotSupported}.
*
* @author Rob Spoor
* @param The element type of the collection to test.
*/
@DisplayName("contains(Object)")
interface ContainsTests extends CollectionTests {
@Test
@DisplayName("contains(Object)")
default void testContains() {
Collection collection = iterable();
for (T o : expectedElements()) {
assertTrue(collection.contains(o));
}
for (T o : nonContainedElements()) {
assertFalse(collection.contains(o));
}
}
@Test
@DisplayName("contains(Object) with null")
default void testContainsWithNull(TestInfo testInfo) {
Collection collection = iterable();
ContainsNullNotSupported annotation = testInfo.getTestClass()
.orElseThrow(() -> new IllegalStateException("test class should be available")) //$NON-NLS-1$
.getAnnotation(ContainsNullNotSupported.class);
if (annotation == null) {
assertFalse(collection.contains(null));
} else {
assertThrows(annotation.expected(), () -> collection.contains(null));
}
}
@Test
@DisplayName("contains(Object) with an incompatible object")
default void testContainsWithIncompatibleObject(TestInfo testInfo) {
Collection collection = iterable();
ContainsIncompatibleNotSupported annotation = testInfo.getTestClass()
.orElseThrow(() -> new IllegalStateException("test class should be available")) //$NON-NLS-1$
.getAnnotation(ContainsIncompatibleNotSupported.class);
if (annotation == null) {
assertFalse(collection.contains(new IncompatibleObject()));
} else {
assertThrows(annotation.expected(), () -> collection.contains(new IncompatibleObject()));
}
}
}
/**
* Contains tests for {@link Collection#toArray()}.
*
* @author Rob Spoor
* @param The element type of the collection to test.
*/
@DisplayName("toArray()")
interface ToObjectArrayTests extends CollectionTests {
@Test
@DisplayName("toArray()")
default void testToObjectArray() {
Collection collection = iterable();
Object[] array = collection.toArray();
assertHasElements(array, expectedElements(), fixedOrder());
}
}
/**
* Contains tests for {@link Collection#toArray(Object[])}.
*
* @author Rob Spoor
* @param The element type of the collection to test.
*/
@DisplayName("toArray(Object[])")
interface ToArrayTests extends CollectionTests {
@Test
@DisplayName("toArray(Object[]) with same length")
default void testToArrayWithSameLength() {
Collection collection = iterable();
Collection expectedElements = expectedElements();
Class> genericType = commonType(expectedElements);
Object[] a = (Object[]) Array.newInstance(genericType, collection.size());
Object[] array = collection.toArray(a);
assertSame(a, array);
assertHasElements(array, expectedElements, fixedOrder());
}
@Test
@DisplayName("toArray(Object[]) with larger length")
default void testToArrayWithLargerLength() {
Collection collection = iterable();
Object[] a = new Object[collection.size() + 2];
Object o = new IncompatibleObject();
Arrays.fill(a, o);
Object[] array = collection.toArray(a);
assertSame(a, array);
assertHasElements(Arrays.copyOfRange(array, 0, collection.size()), expectedElements(), fixedOrder());
assertHasElements(Arrays.copyOfRange(array, collection.size(), a.length), Arrays.asList(null, o), true);
}
@Test
@DisplayName("toArray(Object[]) with smaller length")
default void testToArrayWithSmallerLength() {
Collection collection = iterable();
Collection expectedElements = expectedElements();
Class> genericType = commonType(expectedElements);
Object[] a = (Object[]) Array.newInstance(genericType, 0);
Object[] array = collection.toArray(a);
assertNotSame(a, array);
assertInstanceOf(a.getClass(), array);
assertHasElements(array, expectedElements, fixedOrder());
}
@Test
@DisplayName("toArray(Object[]) with null array")
default void testToArrayWithNullArray() {
Collection collection = iterable();
assertThrows(NullPointerException.class, () -> collection.toArray((Object[]) null));
}
}
// No interface for add - the behaviour is too unspecified to write proper tests. Create for List and Set separately.
/**
* Contains tests for {@link Collection#remove(Object)}.
*
* By default, the tests in this interface assume that calling {@link Collection#remove(Object)} with {@code null} or an instance of an
* incompatible type will simply return {@code false}. If either is not the case, annotate your class with {@link RemoveNullNotSupported} and/or
* {@link RemoveIncompatibleNotSupported}.
*
* @author Rob Spoor
* @param The element type of the collection to test.
*/
@TestInstance(Lifecycle.PER_CLASS)
@DisplayName("remove(Object)")
interface RemoveTests extends CollectionTests {
@ParameterizedTest(name = "{0}: {1}")
@ArgumentsSource(RemoveArgumentsProvider.class)
@DisplayName("remove(Object)")
default void testRemove(Object o, boolean expected) {
Collection collection = iterable();
assertEquals(expected, collection.remove(o));
Collection expectedElements = expectedElements();
if (expected) {
expectedElements = new ArrayList<>(expectedElements);
expectedElements.remove(o);
}
assertHasElements(collection, expectedElements, fixedOrder());
}
@Test
@DisplayName("remove(Object) with null")
default void testRemoveNull(TestInfo testInfo) {
Collection collection = iterable();
RemoveNullNotSupported annotation = testInfo.getTestClass()
.orElseThrow(() -> new IllegalStateException("test class should be available")) //$NON-NLS-1$
.getAnnotation(RemoveNullNotSupported.class);
if (annotation == null) {
assertFalse(collection.remove(null));
} else {
assertThrows(annotation.expected(), () -> collection.remove(null));
}
assertHasElements(collection, expectedElements(), fixedOrder());
}
@Test
@DisplayName("remove(Object) with incompatible object")
default void testRemoveIncompatibleObject(TestInfo testInfo) {
Collection collection = iterable();
RemoveIncompatibleNotSupported annotation = testInfo.getTestClass()
.orElseThrow(() -> new IllegalStateException("test class should be available")) //$NON-NLS-1$
.getAnnotation(RemoveIncompatibleNotSupported.class);
if (annotation == null) {
assertFalse(collection.remove(new IncompatibleObject()));
} else {
assertThrows(annotation.expected(), () -> collection.remove(new IncompatibleObject()));
}
assertHasElements(collection, expectedElements(), fixedOrder());
}
}
/**
* Contains tests for {@link Collection#containsAll(Collection)}.
*
* By default, the tests in this interface assume that calling {@link Collection#containsAll(Collection)} with a collection containing
* {@code null} or an instance of an incompatible type will simply return {@code false}. If either is not the case, annotate your class with
* {@link ContainsNullNotSupported} and/or {@link ContainsIncompatibleNotSupported}.
*
* @author Rob Spoor
* @param The element type of the collection to test.
*/
@TestInstance(Lifecycle.PER_CLASS)
@DisplayName("containsAll(Collection)")
interface ContainsAllTests extends CollectionTests {
@Test
@DisplayName("containsAll(Collection)")
default void testContainsAll() {
Collection collection = iterable();
List expected = new ArrayList<>(expectedElements());
T nonContained = nonContainedElements().iterator().next();
for (int i = 0; i <= expected.size(); i++) {
assertTrue(collection.containsAll(expected.subList(0, i)));
List withNonContained = new ArrayList<>(expected.subList(0, i));
withNonContained.add(nonContained);
assertFalse(collection.containsAll(withNonContained));
}
}
@Test
@DisplayName("containsAll(Collection) with a null collection")
default void testContainsAllWithNullCollection() {
Collection collection = iterable();
assertThrows(NullPointerException.class, () -> collection.containsAll(null));
}
@Test
@DisplayName("containsAll(Collection) with a collection with a null")
default void testContainsAllWithCollectionWithNull(TestInfo testInfo) {
Collection collection = iterable();
Collection c = new ArrayList<>(expectedElements());
c.add(null);
ContainsNullNotSupported annotation = testInfo.getTestClass()
.orElseThrow(() -> new IllegalStateException("test class should be available")) //$NON-NLS-1$
.getAnnotation(ContainsNullNotSupported.class);
if (annotation == null) {
assertFalse(collection.containsAll(c));
} else {
assertThrows(annotation.expected(), () -> collection.containsAll(c));
}
}
@Test
@DisplayName("containsAll(Collection) with a collection with an incompatible object")
default void testContainsAllWithCollectionWithIncompatibleObject(TestInfo testInfo) {
Collection collection = iterable();
Collection