Skip to content

Commit 04872fd

Browse files
authored
Adds MapAssert.hasEntrySatisfying assertion (#76)
Fixes #56
1 parent 0177ba5 commit 04872fd

File tree

3 files changed

+165
-18
lines changed

3 files changed

+165
-18
lines changed

src/main/java/org/assertj/vavr/api/AbstractMapAssert.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import io.vavr.Tuple;
1717
import io.vavr.Tuple2;
1818
import io.vavr.collection.Map;
19+
import org.assertj.core.api.Condition;
1920
import org.assertj.core.api.EnumerableAssert;
2021
import org.assertj.core.internal.ComparatorBasedComparisonStrategy;
2122
import org.assertj.core.internal.ComparisonStrategy;
@@ -69,6 +70,22 @@ public SELF allSatisfy(BiConsumer<? super KEY, ? super VALUE> entryRequirements)
6970
return myself;
7071
}
7172

73+
/**
74+
* Verifies that the actual map contains a value for the given {@code key} that satisfies the given {@code valueCondition}.
75+
*
76+
* @param key the given key to check.
77+
* @param valueCondition the given condition for check value.
78+
* @return {@code this} assertion object.
79+
* @throws NullPointerException if the given values is {@code null}.
80+
* @throws AssertionError if the actual map is {@code null}.
81+
* @throws AssertionError if the actual map not contains the given {@code key}.
82+
* @throws AssertionError if the actual map contains the given key, but value does not match the given {@code valueCondition}.
83+
*/
84+
public SELF hasEntrySatisfying(KEY key, Condition<? super VALUE> valueCondition) {
85+
maps.assertHasEntrySatisfying(info, actual, key, valueCondition);
86+
return myself;
87+
}
88+
7289
@Override
7390
public void isNullOrEmpty() {
7491
if (actual != null && !actual.isEmpty()) throwAssertionError(shouldBeNullOrEmpty(actual));

src/main/java/org/assertj/vavr/internal/Maps.java

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,18 @@
33
import io.vavr.Tuple;
44
import io.vavr.Tuple2;
55
import io.vavr.collection.*;
6+
import io.vavr.control.Option;
67
import org.assertj.core.api.AssertionInfo;
8+
import org.assertj.core.api.Condition;
79
import org.assertj.core.error.ShouldContainAnyOf;
10+
import org.assertj.core.internal.Conditions;
811
import org.assertj.core.internal.Failures;
912
import org.assertj.core.internal.Objects;
1013

1114
import java.util.function.Predicate;
1215

16+
import static io.vavr.Predicates.not;
17+
import static org.assertj.core.error.ElementsShouldBe.elementsShouldBe;
1318
import static org.assertj.core.error.ShouldContain.shouldContain;
1419
import static org.assertj.core.error.ShouldContainExactly.elementsDifferAtIndex;
1520
import static org.assertj.core.error.ShouldContainExactly.shouldContainExactly;
@@ -35,16 +40,42 @@ public final class Maps {
3540

3641
private Failures failures = Failures.instance();
3742

43+
private Conditions conditions = Conditions.instance();
44+
3845
private Maps() {}
3946

4047
public static Maps instance() {
4148
return INSTANCE;
4249
}
4350

51+
/**
52+
* Verifies that the given {@code Map} contains the value for given {@code key} that satisfy given {@code valueCondition}.
53+
*
54+
* @param <K> key type
55+
* @param <V> value type
56+
* @param info contains information about the assertion.
57+
* @param actual the given {@code Map}.
58+
* @param key the given key to check.
59+
* @param valueCondition the given condition for check value.
60+
* @throws NullPointerException if the given values is {@code null}.
61+
* @throws AssertionError if the actual map is {@code null}.
62+
* @throws AssertionError if the actual map not contains the given {@code key}.
63+
* @throws AssertionError if the actual map contains the given key, but value does not match the given {@code valueCondition}.
64+
*/
65+
@SuppressWarnings("unchecked")
66+
public <K, V> void assertHasEntrySatisfying(AssertionInfo info, Map<K, V> actual, K key,
67+
Condition<? super V> valueCondition) {
68+
conditions.assertIsNotNull(valueCondition);
69+
assertContainsKeys(info, actual, key);
70+
Option<V> value = actual.get(key);
71+
value
72+
.filter(valueCondition::matches)
73+
.getOrElseThrow(() -> failures.failure(info, elementsShouldBe(actual, value, valueCondition)));
74+
}
75+
4476
public <K, V> void assertContainsAnyOf(AssertionInfo info, Map<K, V> actual,
4577
Tuple2<K, V>[] entries) {
4678
doCommonContainsCheck(info, actual, entries);
47-
// if both actual and values are empty, then assertion passes.
4879
if (actual.isEmpty() && entries.length == 0) return;
4980
failIfEmptySinceActualIsNotEmpty(entries);
5081
for (Tuple2<? extends K, ? extends V> entry : entries) {
@@ -70,10 +101,9 @@ public <K, V> void assertContainsAnyOf(AssertionInfo info, Map<K, V> actual,
70101
public <K, V> void assertContains(AssertionInfo info, Map<K, V> actual,
71102
Tuple2<K, V>[] entries) {
72103
doCommonContainsCheck(info, actual, entries);
73-
// if both actual and values are empty, then assertion passes.
74104
if (actual.isEmpty() && entries.length == 0) return;
75105
failIfEmptySinceActualIsNotEmpty(entries);
76-
final Set<Tuple2<K, V>> notFound = Array.of(entries).filter(notPresentIn(actual)).toSet();
106+
final Set<Tuple2<K, V>> notFound = Array.of(entries).filter(entryNotPresentIn(actual)).toSet();
77107
if (isNotEmpty(notFound)) {
78108
throw failures.failure(info, shouldContain(actual, entries, notFound));
79109
}
@@ -123,7 +153,7 @@ public <K, V> void assertContainsKeys(AssertionInfo info, Map<K, V> actual,
123153
if (doCommonEmptinessChecks(actual, keys)) return;
124154

125155
Set<K> expected = HashSet.of(keys);
126-
Set<K> notFound = expected.filter(notPresentIn(actual.keySet()));
156+
Set<K> notFound = expected.filter(keyNotPresentIn(actual.keySet()));
127157
if (isNotEmpty(notFound)) {
128158
throw failures.failure(info, shouldContainKeys(actual, notFound.toJavaSet()));
129159
}
@@ -148,7 +178,7 @@ public <K, V> void assertDoesNotContainKeys(AssertionInfo info, Map<K, V> actual
148178
if (doCommonEmptinessChecks(actual, keys)) return;
149179

150180
Set<K> expected = HashSet.of(keys);
151-
Set<K> found = expected.filter(presentIn(actual.keySet()));
181+
Set<K> found = expected.filter(keyPresentIn(actual.keySet()));
152182
if (isNotEmpty(found)) {
153183
throw failures.failure(info, shouldNotContainKeys(actual, found.toJavaSet()));
154184
}
@@ -171,14 +201,12 @@ public <K, V> void assertDoesNotContainKeys(AssertionInfo info, Map<K, V> actual
171201
public <K, V> void assertContainsOnly(AssertionInfo info, Map<K, V> actual, Iterable<Tuple2<K, V>> entries) {
172202
assertNotNull(info, actual);
173203
failIfNull(entries);
174-
if (actual.isEmpty() && !entries.iterator().hasNext()) {
175-
return;
176-
}
204+
if (actual.isEmpty() && !entries.iterator().hasNext()) return;
177205
failIfEmpty(entries);
178206
Map<K, V> expected = HashMap.ofEntries(entries);
179-
Map<K, V> notExpected = actual.filter(notPresentIn(expected));
207+
Map<K, V> notExpected = actual.filter(entryNotPresentIn(expected));
180208
if (isNotEmpty(notExpected)) {
181-
Map<K, V> notFound = expected.filter(notPresentIn(actual));
209+
Map<K, V> notFound = expected.filter(entryNotPresentIn(actual));
182210
throw failures.failure(info, shouldContainOnly(actual, expected, notFound, notExpected));
183211
}
184212
}
@@ -246,9 +274,9 @@ public <K, V> void assertContainsOnlyKeys(AssertionInfo info, Map<K, V> actual,
246274
if (doCommonEmptinessChecks(actual, keys)) return;
247275

248276
Set<K> expected = HashSet.of(keys);
249-
Set<K> notExpected = actual.keySet().filter(notPresentIn(expected));
277+
Set<K> notExpected = actual.keySet().filter(keyNotPresentIn(expected));
250278
if (isNotEmpty(notExpected)) {
251-
Set<K> notFound = expected.filter(notPresentIn(actual.keySet()));
279+
Set<K> notFound = expected.filter(keyNotPresentIn(actual.keySet()));
252280
throw failures.failure(info, shouldContainOnlyKeys(actual, expected, notFound, notExpected));
253281
}
254282
}
@@ -420,24 +448,24 @@ private static <K, V> Map<K, V> asLinkedMap(Tuple2<? extends K, ? extends V>[] e
420448
return Array.of(entries).filter(java.util.Objects::nonNull);
421449
}
422450

423-
private static <K, V> Predicate<Tuple2<K, V>> notPresentIn(Map<K, V> map) {
451+
private static <K, V> Predicate<Tuple2<K, V>> entryNotPresentIn(Map<K, V> map) {
424452
return tuple -> !map.contains(tuple);
425453
}
426454

427-
private static <K> Predicate<K> notPresentIn(Set<K> elements) {
428-
return elem -> !elements.contains(elem);
455+
private static <K> Predicate<K> keyNotPresentIn(Set<K> elements) {
456+
return not(keyPresentIn(elements));
429457
}
430458

431-
private static <K> Predicate<K> presentIn(Set<K> elements) {
459+
private static <K> Predicate<K> keyPresentIn(Set<K> elements) {
432460
return elements::contains;
433461
}
434462

435-
private <V> Predicate<V> valuePresentIn(Seq<V> elements) {
463+
private static <V> Predicate<V> valuePresentIn(Seq<V> elements) {
436464
return elements::contains;
437465
}
438466

439467
private static <V> Predicate<V> valueNotPresentIn(Seq<V> elements) {
440-
return elem -> !elements.contains(elem);
468+
return not(valuePresentIn(elements));
441469
}
442470

443471
private static boolean isNotEmpty(Traversable traversable) {
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package org.assertj.vavr.api;
2+
3+
/*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
* <p>
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
* <p>
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
* <p>
13+
* Copyright 2012-2019 the original author or authors.
14+
*/
15+
16+
import io.vavr.collection.HashMap;
17+
import io.vavr.collection.Map;
18+
import org.assertj.core.api.Condition;
19+
import org.junit.jupiter.api.Test;
20+
21+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
22+
import static org.assertj.core.error.ShouldNotBeNull.shouldNotBeNull;
23+
import static org.assertj.vavr.api.VavrAssertions.assertThat;
24+
25+
@SuppressWarnings("unchecked")
26+
class MapAssert_hasEntrySatisfying_Test {
27+
28+
private Condition<String> passingCondition = new TestCondition<>(true);
29+
private Condition<String> notPassingCondition = new TestCondition<>();
30+
31+
@Test
32+
void should_pass_if_Map_contains_entry_satisfying_condition() {
33+
final Map<String, String> actual = HashMap.of(
34+
"key1", "value1", "key2", "value2"
35+
);
36+
37+
assertThat(actual).hasEntrySatisfying("key1", passingCondition);
38+
}
39+
40+
@Test
41+
void should_fail_if_Map_is_empty() {
42+
final Map<String, String> actual = HashMap.empty();
43+
44+
assertThatThrownBy(
45+
() -> assertThat(actual).hasEntrySatisfying("key1", passingCondition)
46+
)
47+
.isInstanceOf(AssertionError.class)
48+
.hasMessage("\nExpecting:\n" +
49+
" <HashMap()>\n" +
50+
"to contain key:\n" +
51+
" <\"key1\">");
52+
}
53+
54+
@Test
55+
void should_fail_when_Map_is_null() {
56+
assertThatThrownBy(
57+
() -> assertThat((Map<String, String>) null).hasEntrySatisfying("key1", passingCondition)
58+
)
59+
.isInstanceOf(AssertionError.class)
60+
.hasMessage(shouldNotBeNull().create());
61+
}
62+
63+
@Test
64+
void should_fail_when_Map_does_not_contain_given_key() {
65+
final Map<String, String> actual = HashMap.of(
66+
"key1", "value1", "key2", "value2"
67+
);
68+
69+
assertThatThrownBy(
70+
() -> assertThat(actual).hasEntrySatisfying("key3", passingCondition)
71+
)
72+
.isInstanceOf(AssertionError.class)
73+
.hasMessage("\nExpecting:\n" +
74+
" <HashMap((key1, value1), (key2, value2))>\n" +
75+
"to contain key:\n" +
76+
" <\"key3\">");
77+
}
78+
79+
@Test
80+
void should_fail_when_condition_is_null() {
81+
final Map<String, String> actual = HashMap.of(
82+
"key1", "value1", "key2", "value2"
83+
);
84+
85+
assertThatThrownBy(
86+
() -> assertThat(actual).hasEntrySatisfying("key1", (Condition<String>) null)
87+
)
88+
.isInstanceOf(NullPointerException.class)
89+
.hasMessage("The condition to evaluate should not be null");
90+
}
91+
92+
@Test
93+
void should_fail_if_Map_does_not_contain_entry_satisfying_condition() {
94+
final Map<String, String> actual = HashMap.of("key1", "value1", "key3", "value3");
95+
96+
assertThatThrownBy(
97+
() -> assertThat(actual).hasEntrySatisfying("key1", notPassingCondition)
98+
)
99+
.isInstanceOf(AssertionError.class)
100+
.hasMessage("\nExpecting elements:\n<Some(value1)>\n of \n<HashMap((key1, value1), (key3, value3))>\n to be <TestCondition>");
101+
}
102+
}

0 commit comments

Comments
 (0)