Skip to content

Commit be4cf7c

Browse files
authored
Add isUnmodifiable to Iterator assertions (#3477)
1 parent 6e760db commit be4cf7c

2 files changed

Lines changed: 189 additions & 0 deletions

File tree

assertj-core/src/main/java/org/assertj/core/api/AbstractIteratorAssert.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@
1212
*/
1313
package org.assertj.core.api;
1414

15+
import static org.assertj.core.error.ShouldBeUnmodifiable.shouldBeUnmodifiable;
16+
1517
import java.util.Iterator;
1618

19+
import org.assertj.core.annotations.Beta;
1720
import org.assertj.core.internal.Iterators;
1821
import org.assertj.core.util.VisibleForTesting;
1922

@@ -93,4 +96,50 @@ public IterableAssert<ELEMENT> toIterable() {
9396
return new IterableAssert<>(IterableAssert.toIterable(actual));
9497
}
9598

99+
/**
100+
* Verifies that the actual iterator is unmodifiable, i.e., throws an {@link UnsupportedOperationException} with
101+
* any attempt to remove from the iterator.
102+
* <p>
103+
* Example:
104+
* <pre><code class='java'> // assertions will pass
105+
* assertThat(List.of().iterator()).isUnmodifiable();
106+
* assertThat(Set.of().iterator()).isUnmodifiable();
107+
*
108+
* // assertions will fail
109+
* assertThat(new ArrayList&lt;&gt;().iterator()).isUnmodifiable();
110+
* assertThat(new HashSet&lt;&gt;().iterator()).isUnmodifiable();</code></pre>
111+
*
112+
* @return {@code this} assertion object.
113+
* @throws AssertionError if the actual iterator is modifiable.
114+
* @since 3.26.0
115+
*/
116+
@Beta
117+
public SELF isUnmodifiable() {
118+
isNotNull();
119+
assertIsUnmodifiable();
120+
return myself;
121+
}
122+
123+
private void assertIsUnmodifiable() {
124+
switch (actual.getClass().getName()) {
125+
case "java.util.Collections$EmptyIterator":
126+
case "java.util.Collections$EmptyListIterator":
127+
// immutable by contract, although not all methods throw UnsupportedOperationException
128+
return;
129+
}
130+
131+
expectUnsupportedOperationException(actual::remove, "Iterator.remove()");
132+
}
133+
134+
// Same as AbstractCollectionAssert#expectUnsupportedOperationException
135+
private void expectUnsupportedOperationException(Runnable runnable, String method) {
136+
try {
137+
runnable.run();
138+
throwAssertionError(shouldBeUnmodifiable(method));
139+
} catch (UnsupportedOperationException e) {
140+
// happy path
141+
} catch (RuntimeException e) {
142+
throwAssertionError(shouldBeUnmodifiable(method, e));
143+
}
144+
}
96145
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
3+
* the License. You may obtain a copy of the License at
4+
*
5+
* http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
8+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
9+
* specific language governing permissions and limitations under the License.
10+
*
11+
* Copyright 2012-2024 the original author or authors.
12+
*/
13+
package org.assertj.core.api.iterator;
14+
15+
import static org.assertj.core.api.Assertions.assertThat;
16+
import static org.assertj.core.api.Assertions.assertThatNoException;
17+
import static org.assertj.core.api.BDDAssertions.then;
18+
import static org.assertj.core.error.ShouldBeUnmodifiable.shouldBeUnmodifiable;
19+
import static org.assertj.core.error.ShouldNotBeNull.shouldNotBeNull;
20+
import static org.assertj.core.util.AssertionsUtil.expectAssertionError;
21+
import static org.assertj.core.util.Lists.list;
22+
import static org.assertj.core.util.Lists.newArrayList;
23+
import static org.assertj.core.util.Sets.newLinkedHashSet;
24+
import static org.assertj.core.util.Sets.newTreeSet;
25+
import static org.assertj.core.util.Sets.set;
26+
import static org.junit.jupiter.params.provider.Arguments.arguments;
27+
28+
import java.util.ArrayList;
29+
import java.util.Collections;
30+
import java.util.HashSet;
31+
import java.util.Iterator;
32+
import java.util.LinkedHashSet;
33+
import java.util.LinkedList;
34+
import java.util.stream.Stream;
35+
36+
import org.apache.commons.collections4.collection.UnmodifiableCollection;
37+
import org.apache.commons.collections4.list.UnmodifiableList;
38+
import org.apache.commons.collections4.set.UnmodifiableSet;
39+
import org.apache.commons.collections4.set.UnmodifiableSortedSet;
40+
import org.assertj.core.error.ErrorMessageFactory;
41+
import org.assertj.core.test.jdk11.Jdk11;
42+
import org.junit.jupiter.api.Test;
43+
import org.junit.jupiter.params.ParameterizedTest;
44+
import org.junit.jupiter.params.provider.Arguments;
45+
import org.junit.jupiter.params.provider.MethodSource;
46+
47+
import com.google.common.collect.ImmutableList;
48+
import com.google.common.collect.ImmutableSet;
49+
import com.google.common.collect.ImmutableSortedSet;
50+
import com.google.common.collect.Sets;
51+
52+
class IteratorAssert_isUnmodifiable_Test {
53+
54+
@Test
55+
void should_fail_if_actual_is_null() {
56+
// GIVEN
57+
Iterator<?> actual = null;
58+
// WHEN
59+
AssertionError assertionError = expectAssertionError(() -> assertThat(actual).isUnmodifiable());
60+
// THEN
61+
then(assertionError).hasMessage(shouldNotBeNull().create());
62+
}
63+
64+
@ParameterizedTest
65+
@MethodSource({ "modifiableIterators", "startedModifiableIterators" })
66+
void should_fail_if_actual_can_be_modified(Iterator<?> actual, ErrorMessageFactory errorMessageFactory) {
67+
// WHEN
68+
AssertionError assertionError = expectAssertionError(() -> assertThat(actual).isUnmodifiable());
69+
// THEN
70+
then(assertionError).as(actual.getClass().getName())
71+
.hasMessage(errorMessageFactory.create());
72+
}
73+
74+
// Same as CollectionAssert_isUnmodifiable_Test#modifiableCollections
75+
private static Stream<Arguments> modifiableIterators() {
76+
return Stream.of(arguments(new ArrayList<>().iterator(),
77+
shouldBeUnmodifiable("Iterator.remove()", new IllegalStateException())),
78+
arguments(new LinkedHashSet<>().iterator(),
79+
shouldBeUnmodifiable("Iterator.remove()", new IllegalStateException())),
80+
arguments(new LinkedList<>().iterator(),
81+
shouldBeUnmodifiable("Iterator.remove()", new IllegalStateException())),
82+
arguments(new HashSet<>().iterator(),
83+
shouldBeUnmodifiable("Iterator.remove()", new IllegalStateException())),
84+
arguments(newArrayList(new Object()).iterator(),
85+
shouldBeUnmodifiable("Iterator.remove()", new IllegalStateException())),
86+
arguments(newLinkedHashSet(new Object()).iterator(),
87+
shouldBeUnmodifiable("Iterator.remove()", new IllegalStateException())),
88+
arguments(newTreeSet("element").iterator(),
89+
shouldBeUnmodifiable("Iterator.remove()", new IllegalStateException())));
90+
}
91+
92+
// No exception thrown if Iterator.remove() is called after Iterator.next()
93+
private static Stream<Arguments> startedModifiableIterators() {
94+
Iterator<?> startedIterator = new ArrayList<>(list(1, 2, 3)).iterator();
95+
startedIterator.next();
96+
Iterator<?> endedIterator = new ArrayList<>(list(1)).iterator();
97+
endedIterator.next();
98+
return Stream.of(arguments(startedIterator, shouldBeUnmodifiable("Iterator.remove()")),
99+
arguments(endedIterator, shouldBeUnmodifiable("Iterator.remove()")));
100+
}
101+
102+
@ParameterizedTest
103+
@MethodSource("unmodifiableIterators")
104+
void should_pass(Iterator<?> actual) {
105+
// WHEN/THEN
106+
assertThatNoException().as(actual.getClass().getName())
107+
.isThrownBy(() -> assertThat(actual).isUnmodifiable());
108+
}
109+
110+
// Same as CollectionAssert_isUnmodifiable_Test#unmodifiableCollections
111+
private static Stream<Iterator<?>> unmodifiableIterators() {
112+
return Stream.of(Collections.emptyList(),
113+
Collections.emptyNavigableSet(),
114+
Collections.emptySet(),
115+
Collections.emptySortedSet(),
116+
Collections.singleton("element"),
117+
Collections.singletonList("element"),
118+
Collections.unmodifiableCollection(list(new Object())),
119+
Collections.unmodifiableList(list(new Object())),
120+
Collections.unmodifiableNavigableSet(newTreeSet("element")),
121+
Collections.unmodifiableSet(set(new Object())),
122+
Collections.unmodifiableSortedSet(newTreeSet("element")),
123+
ImmutableList.of(new Object()),
124+
ImmutableSet.of(new Object()),
125+
ImmutableSortedSet.of("element"),
126+
Jdk11.List.of(),
127+
Jdk11.List.of("element"), // same implementation for 1 or 2 parameters
128+
Jdk11.List.of("element", "element", "element"), // same implementation for 3+ parameters
129+
Jdk11.Set.of(),
130+
Jdk11.Set.of("element"), // same implementation for 1 or 2 parameters
131+
Jdk11.Set.of("element1", "element2", "element3"), // same implementation for 3+ parameters
132+
Sets.unmodifiableNavigableSet(newTreeSet("element")),
133+
UnmodifiableCollection.unmodifiableCollection(list(new Object())),
134+
UnmodifiableList.unmodifiableList(list(new Object())),
135+
UnmodifiableSet.unmodifiableSet(set(new Object())),
136+
UnmodifiableSortedSet.unmodifiableSortedSet(newTreeSet("element")))
137+
.map(c -> c.iterator());
138+
}
139+
140+
}

0 commit comments

Comments
 (0)