diff --git a/README.md b/README.md
new file mode 100644
index 0000000..cf76cca
--- /dev/null
+++ b/README.md
@@ -0,0 +1,3 @@
+# AssertJ Eclipse Collections [](https://www.repostatus.org/#wip)
+
+[](https://github.com/assertj/assertj/actions/workflows/main.yml?query=branch%3Amain)
diff --git a/pom.xml b/pom.xml
index 7658305..9fefaee 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,6 +23,14 @@
+
+
+ mattbertolini
+ Matt Bertolini
+ https://github.com/mattbertolini
+
+
+
scm:git:https://github.com/assertj/assertj-eclipse-collections.gitscm:git:https://github.com/assertj/assertj-eclipse-collections.git
@@ -31,9 +39,10 @@
3.27.3
+ 1113.0.0
- 5.11.4
+ 5.12.1
@@ -46,10 +55,16 @@
org.eclipse.collections
- eclipse-collections-api
+ eclipse-collections${eclipse-collections.version}provided
+
+ org.opentest4j
+ opentest4j
+ provided
+ true
+ org.junit.jupiter
diff --git a/src/main/java/org/assertj/eclipse/collections/Main.java b/src/main/java/org/assertj/eclipse/collections/Main.java
deleted file mode 100644
index 05edd14..0000000
--- a/src/main/java/org/assertj/eclipse/collections/Main.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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 2025-2025 the original author or authors.
- */
-package org.assertj.eclipse.collections;
-
-/**
- * Main
- * @see org.assertj.core.api.Assertions
- */
-public class Main {
-
- /**
- * main
- *
- * @param args args
- */
- public static void main(String[] args) {
- System.out.println("Hello, World!");
- }
-
- private Main() {
- }
-
-}
diff --git a/src/main/java/org/assertj/eclipse/collections/api/Assertions.java b/src/main/java/org/assertj/eclipse/collections/api/Assertions.java
new file mode 100644
index 0000000..21cf91f
--- /dev/null
+++ b/src/main/java/org/assertj/eclipse/collections/api/Assertions.java
@@ -0,0 +1,43 @@
+/*
+ * 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 2025-2025 the original author or authors.
+ */
+package org.assertj.eclipse.collections.api;
+
+import org.assertj.core.util.CheckReturnValue;
+import org.assertj.eclipse.collections.api.multimap.MultimapAssert;
+import org.eclipse.collections.api.multimap.Multimap;
+
+/**
+ * Entry point for assertion methods for the Eclipse Collections library. Each method in this class is a static factory
+ * for a type-specific assertion object.
+ */
+@CheckReturnValue
+public class Assertions {
+ /**
+ * Creates a new {@link Assertions}.
+ */
+ protected Assertions() {
+ // Do nothing
+ }
+
+ /**
+ * Creates a new instance of {@link MultimapAssert}.
+ *
+ * @param actual the actual value.
+ * @return the created assertion object.
+ * @param The type of keys in the BagMultimap
+ * @param The type of values in the BagMultimap
+ */
+ public static MultimapAssert assertThat(Multimap actual) {
+ return new MultimapAssert<>(actual);
+ }
+}
diff --git a/src/main/java/org/assertj/eclipse/collections/api/BDDAssertions.java b/src/main/java/org/assertj/eclipse/collections/api/BDDAssertions.java
new file mode 100644
index 0000000..719f656
--- /dev/null
+++ b/src/main/java/org/assertj/eclipse/collections/api/BDDAssertions.java
@@ -0,0 +1,43 @@
+/*
+ * 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 2025-2025 the original author or authors.
+ */
+package org.assertj.eclipse.collections.api;
+
+import org.assertj.core.util.CheckReturnValue;
+import org.assertj.eclipse.collections.api.multimap.MultimapAssert;
+import org.eclipse.collections.api.multimap.Multimap;
+
+/**
+ * Behavior-driven development style entry point for assertion methods for the Eclipse Collections library. Each method
+ * in this class is a static factory for a type-specific assertion object.
+ */
+@CheckReturnValue
+public class BDDAssertions extends Assertions {
+ /**
+ * Creates a new {@link BDDAssertions}.
+ */
+ protected BDDAssertions() {
+ // Do nothing
+ }
+
+ /**
+ * Creates a new instance of {@link MultimapAssert}.
+ *
+ * @param actual the actual value.
+ * @return the created assertion object.
+ * @param The type of keys in the BagMultimap
+ * @param The type of values in the BagMultimap
+ */
+ public static MultimapAssert then(Multimap actual) {
+ return assertThat(actual);
+ }
+}
diff --git a/src/main/java/org/assertj/eclipse/collections/api/EclipseCollectionsSoftAssertionsProvider.java b/src/main/java/org/assertj/eclipse/collections/api/EclipseCollectionsSoftAssertionsProvider.java
new file mode 100644
index 0000000..e9b7cb8
--- /dev/null
+++ b/src/main/java/org/assertj/eclipse/collections/api/EclipseCollectionsSoftAssertionsProvider.java
@@ -0,0 +1,37 @@
+/*
+ * 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 2025-2025 the original author or authors.
+ */
+package org.assertj.eclipse.collections.api;
+
+import org.assertj.core.api.SoftAssertionsProvider;
+import org.assertj.core.util.CheckReturnValue;
+import org.assertj.eclipse.collections.api.multimap.MultimapAssert;
+import org.eclipse.collections.api.multimap.Multimap;
+
+/**
+ * Soft assertions implementations for Eclipse Collections types.
+ */
+@CheckReturnValue
+public interface EclipseCollectionsSoftAssertionsProvider extends SoftAssertionsProvider {
+ /**
+ * Creates a new, proxied instance of a {@link MultimapAssert}
+ *
+ * @param actual the path
+ * @return the created assertion object
+ * @param The type of keys in the actual BagMultimap
+ * @param The type of values in the actual BagMultimap
+ */
+ @SuppressWarnings("unchecked")
+ default MultimapAssert assertThat(Multimap actual) {
+ return this.proxy(MultimapAssert.class, Multimap.class, actual);
+ }
+}
diff --git a/src/main/java/org/assertj/eclipse/collections/api/SoftAssertions.java b/src/main/java/org/assertj/eclipse/collections/api/SoftAssertions.java
new file mode 100644
index 0000000..abc0c64
--- /dev/null
+++ b/src/main/java/org/assertj/eclipse/collections/api/SoftAssertions.java
@@ -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 2025-2025 the original author or authors.
+ */
+package org.assertj.eclipse.collections.api;
+
+import org.assertj.core.api.AbstractSoftAssertions;
+import org.assertj.core.api.SoftAssertionsProvider;
+import org.opentest4j.MultipleFailuresError;
+
+import java.util.function.Consumer;
+
+/**
+ * A soft assertions provider for Eclipse Collections assertions
+ */
+public class SoftAssertions extends AbstractSoftAssertions implements EclipseCollectionsSoftAssertionsProvider {
+ /**
+ * Creates a new {@link SoftAssertions}.
+ */
+ public SoftAssertions() {
+ // Do nothing
+ }
+
+ /**
+ * Convenience method for calling {@link EclipseCollectionsSoftAssertionsProvider#assertSoftly} for these assertion
+ * types. Equivalent to {@code SoftAssertion.assertSoftly(SoftAssertions.class, softly)}.
+ *
+ * @param softly the Consumer containing the code that will make the soft assertions.
+ * Takes one parameter (the SoftAssertions instance used to make the assertions).
+ * @throws MultipleFailuresError if possible or SoftAssertionError if any proxied assertion objects threw an {@link
+ * AssertionError}
+ */
+ public static void assertSoftly(Consumer softly) {
+ SoftAssertionsProvider.assertSoftly(SoftAssertions.class, softly);
+ }
+}
diff --git a/src/main/java/org/assertj/eclipse/collections/api/multimap/MultimapAssert.java b/src/main/java/org/assertj/eclipse/collections/api/multimap/MultimapAssert.java
new file mode 100644
index 0000000..0b69d65
--- /dev/null
+++ b/src/main/java/org/assertj/eclipse/collections/api/multimap/MultimapAssert.java
@@ -0,0 +1,555 @@
+/*
+ * 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 2025-2025 the original author or authors.
+ */
+package org.assertj.eclipse.collections.api.multimap;
+
+import static java.util.Objects.requireNonNull;
+import static org.assertj.core.error.ShouldBeEmpty.shouldBeEmpty;
+import static org.assertj.core.error.ShouldBeNullOrEmpty.shouldBeNullOrEmpty;
+import static org.assertj.core.error.ShouldContain.shouldContain;
+import static org.assertj.core.error.ShouldContainKey.shouldContainKey;
+import static org.assertj.core.error.ShouldContainKeys.shouldContainKeys;
+import static org.assertj.core.error.ShouldContainOnly.shouldContainOnly;
+import static org.assertj.core.error.ShouldContainValue.shouldContainValue;
+import static org.assertj.core.error.ShouldContainValues.shouldContainValues;
+import static org.assertj.core.error.ShouldHaveSize.shouldHaveSize;
+import static org.assertj.core.error.ShouldHaveSizeBetween.shouldHaveSizeBetween;
+import static org.assertj.core.error.ShouldHaveSizeGreaterThan.shouldHaveSizeGreaterThan;
+import static org.assertj.core.error.ShouldHaveSizeGreaterThanOrEqualTo.shouldHaveSizeGreaterThanOrEqualTo;
+import static org.assertj.core.error.ShouldHaveSizeLessThan.shouldHaveSizeLessThan;
+import static org.assertj.core.error.ShouldHaveSizeLessThanOrEqualTo.shouldHaveSizeLessThanOrEqualTo;
+import static org.assertj.core.error.ShouldNotBeEmpty.shouldNotBeEmpty;
+import static org.assertj.eclipse.collections.error.ShouldHaveDistinctSize.shouldHaveDistinctSize;
+import static org.assertj.eclipse.collections.error.ShouldHaveDistinctSizeGreaterThan.shouldHaveDistinctSizeGreaterThan;
+import static org.assertj.eclipse.collections.error.ShouldHaveDistinctSizeGreaterThanOrEqualTo.shouldHaveDistinctSizeGreaterThanOrEqualTo;
+
+import java.util.Map;
+
+import org.assertj.core.api.AbstractAssert;
+import org.assertj.core.api.Condition;
+import org.assertj.core.error.GroupTypeDescription;
+import org.eclipse.collections.api.RichIterable;
+import org.eclipse.collections.api.factory.Lists;
+import org.eclipse.collections.api.list.MutableList;
+import org.eclipse.collections.api.multimap.Multimap;
+import org.eclipse.collections.api.partition.list.PartitionMutableList;
+import org.eclipse.collections.api.tuple.Pair;
+import org.eclipse.collections.impl.tuple.Tuples;
+
+/**
+ * Base class for all implementations of assertions for {@link Multimap}.
+ *
+ * @param the type of keys in the Multimap.
+ * @param the type of values in the Multimap.
+ */
+public class MultimapAssert extends AbstractAssert, Multimap> {
+
+ /**
+ * Creates a new {@link MultimapAssert}.
+ *
+ * @param actual The actual Multimap to assert against
+ */
+ public MultimapAssert(Multimap actual) {
+ super(actual, MultimapAssert.class);
+ }
+
+ /**
+ * Verifies that the actual {@link Multimap} contains the given entries. Entries are given in the form of {@link
+ * Pair} objects.
+ *
+ *
+ * @param entries the entries that are expected to be present in the {@link Multimap}.
+ * @return this assertion object for method chaining.
+ * @throws AssertionError if the actual {@link Multimap} does not contain the given entries
+ */
+ @SafeVarargs
+ public final MultimapAssert contains(Pair... entries) {
+ return this.containsForProxy(Lists.mutable.of(entries));
+ }
+
+ /**
+ * Verifies that the actual {@code Multimap} contains the provided entries. Entries are provided as
+ * an array of {@code Map.Entry} objects.
+ *
+ * @param entries the entries that are expected to be contained within the {@code Multimap}.
+ * @return this assertion object for method chaining.
+ * @throws AssertionError if the actual {@code Multimap} does not contain one or more of the specified entries.
+ */
+ @SafeVarargs
+ public final MultimapAssert contains(Map.Entry... entries) {
+ MutableList> pairs = Lists.mutable.of(entries).collect(Tuples::pairFrom);
+ return this.containsForProxy(pairs);
+ }
+
+ /**
+ * Verifies that the actual {@code Multimap} contains the provided entries.
+ *
+ * @param entries the list of entries that are expected to be contained within the {@code Multimap}.
+ * @return this assertion object for method chaining.
+ * @throws AssertionError if the actual {@code Multimap} does not contain one or more of the specified entries.
+ */
+ protected MultimapAssert containsForProxy(MutableList> entries) {
+ this.isNotNull();
+ MutableList> entriesNotFound = entries
+ .reject(entry -> this.actual.containsKeyAndValue(entry.getOne(), entry.getTwo()));
+ if (entriesNotFound.isEmpty()) {
+ return this.myself;
+ }
+ throw this.assertionError(shouldContain(this.actual, entries, entriesNotFound));
+ }
+
+ /**
+ * Verifies that the actual {@link Multimap} contains the given key-value entry.
+ *
+ * @param key the key that is expected to be present in the {@link Multimap}.
+ * @param value the value that is expected to be associated with the given key in the {@link Multimap}.
+ * @return this assertion object for method chaining.
+ * @throws AssertionError if the actual {@link Multimap} does not contain the given key-value entry.
+ * @see #contains(Pair[])
+ * @see #contains(Map.Entry[])
+ */
+ public MultimapAssert containsEntry(KEY key, VALUE value) {
+ return this.contains(Tuples.pair(key, value));
+ }
+
+ /**
+ * Verifies that the actual {@link Multimap} contains the given keys.
+ *
+ *
+ * @param keys the keys that are expected to be present in the {@link Multimap}.
+ * @return this assertion object for method chaining.
+ * @throws AssertionError if the actual {@link Multimap} does not contain the given keys.
+ */
+ public MultimapAssert containsKeys(KEY... keys) {
+ this.isNotNull();
+ MutableList keysNotFound = Lists.mutable.of(keys).reject(this.actual::containsKey);
+ if (keysNotFound.isEmpty()) {
+ return this.myself;
+ }
+ throw this.assertionError(shouldContainKeys(this.actual, keysNotFound.toSet()));
+ }
+
+ /**
+ * Verifies that the map contains only the given entries.
+ *
+ * @param entries the array of map entries to validate against the map
+ * @return this assertion object for method chaining
+ * @throws AssertionError if the actual {@link Multimap} does not contain only the given entries.
+ */
+ @SafeVarargs
+ public final MultimapAssert containsOnly(Map.Entry extends KEY, ? extends VALUE>... entries) {
+ MutableList> pairs = Lists.mutable.of(entries).collect(Tuples::pairFrom);
+ return this.containsOnlyForProxy(pairs);
+ }
+
+ /**
+ * Verifies that the current object contains only the specified pairs of key-value entries.
+ *
+ * @param entries the pairs of key-value entries to check against
+ * @return this assertion object for method chaining
+ * @throws AssertionError if the actual {@link Multimap} does not contain only the given entries.
+ */
+ @SafeVarargs
+ public final MultimapAssert containsOnly(Pair extends KEY, ? extends VALUE>... entries) {
+ return this.containsOnlyForProxy(Lists.mutable.of(entries));
+ }
+
+ /**
+ * Verifies that the current object contains only the specified list of pairs of key-value entries.
+ *
+ * @param entries the list of pairs of key-value entries to check against
+ * @return this assertion object for method chaining
+ * @throws AssertionError if the actual {@link Multimap} does not contain only the given entries.
+ */
+ protected MultimapAssert containsOnlyForProxy(MutableList> entries) {
+ this.isNotNull();
+ PartitionMutableList> partition = entries
+ .partition(entry -> this.actual.containsKeyAndValue(entry.getOne(), entry.getTwo()));
+
+ MutableList> found = partition.getSelected();
+ MutableList> notFound = partition.getRejected();
+ RichIterable> notExpected = this.actual.keyValuePairsView().reject(found::contains);
+
+ if (notFound.isEmpty() && notExpected.isEmpty()) {
+ return this.myself;
+ }
+
+ GroupTypeDescription groupTypeDescription = new GroupTypeDescription("multimap", "multimap entries");
+ throw this.assertionError(shouldContainOnly(this.actual, entries, notFound, notExpected, groupTypeDescription));
+ }
+
+ /**
+ * Verifies that the actual map contains the given values.
+ *
+ * @param values the values expected to be present in the actual map
+ * @return the current assertion object for method chaining
+ * @throws AssertionError if the actual map does not contain the given values
+ */
+ public MultimapAssert containsValues(VALUE... values) {
+ this.isNotNull();
+ MutableList valuesNotFound = Lists.mutable.of(values).reject(this.actual::containsValue);
+ if (valuesNotFound.isEmpty()) {
+ return this.myself;
+ }
+ throw this.assertionError(shouldContainValues(this.actual, valuesNotFound.toSet()));
+ }
+
+ /**
+ * Verifies that the actual {@code Multimap} has the expected number of distinct keys.
+ *
+ * @param expected the expected number of distinct keys in the {@code Multimap}.
+ * @return this assertion object for method chaining.
+ * @throws AssertionError if the actual number of distinct keys in the {@code Multimap} does not match the expected size.
+ */
+ public MultimapAssert hasDistinctSize(int expected) {
+ this.isNotNull();
+ int actualSize = this.actual.sizeDistinct();
+ if (actualSize == expected) {
+ return this.myself;
+ }
+ throw this.assertionError(shouldHaveDistinctSize(this.actual, actualSize, expected));
+ }
+
+ /**
+ * Verifies that the distinct size of the {@link Multimap} is greater than the specified boundary.
+ *
+ * @param boundary the size that the actual number of distinct keys should exceed.
+ * @return this assertion object for method chaining.
+ * @throws AssertionError if the actual distinct size of the {@link Multimap} is not greater than the specified boundary.
+ */
+ public MultimapAssert hasDistinctSizeGreaterThan(int boundary) {
+ this.isNotNull();
+ int actualSize = this.actual.sizeDistinct();
+ if (actualSize > boundary) {
+ return this.myself;
+ }
+ throw this.assertionError(shouldHaveDistinctSizeGreaterThan(this.actual, actualSize, boundary));
+ }
+
+ /**
+ * Verifies that the distinct size of the {@link Multimap} is greater than or equal to the specified boundary.
+ *
+ * @param boundary the minimum distinct size to compare the Multimap against
+ * @return this assertion for method chaining
+ * @throws AssertionError if the distinct size of the collection is less than the specified boundary
+ */
+ public MultimapAssert hasDistinctSizeGreaterThanOrEqualTo(int boundary) {
+ this.isNotNull();
+ int actualSize = this.actual.sizeDistinct();
+ if (actualSize >= boundary) {
+ return this.myself;
+ }
+ throw this.assertionError(shouldHaveDistinctSizeGreaterThanOrEqualTo(this.actual, actualSize, boundary));
+ }
+
+ /**
+ * Verifies that at least one key in the actual {@link Multimap} satisfies the given condition.
+ *
+ * @param keyCondition the condition to evaluate the keys against; must not be null.
+ * @return this assertion object for method chaining.
+ * @throws NullPointerException if the provided condition is null.
+ * @throws AssertionError if none of the keys in the {@link Multimap} satisfy the given condition.
+ */
+ public MultimapAssert hasKeySatisfying(Condition super KEY> keyCondition) {
+ this.isNotNull();
+ requireNonNull(keyCondition, "The condition to evaluate should not be null");
+
+ if (this.actual.keysView().anySatisfy(keyCondition::matches)) {
+ return this.myself;
+ }
+
+ throw this.assertionError(shouldContainKey(this.actual, keyCondition));
+ }
+
+ /**
+ * Verifies that the number of key-value entry pairs in the {@link Multimap} is equal to the given one.
+ *
+ *
+ * @param expected the expected size of the {@link Multimap}.
+ * @return {@code this} assertion object.
+ * @throws AssertionError if the actual size of the {@link Multimap} is not equal to the expected size.
+ */
+ public MultimapAssert hasSize(int expected) {
+ this.isNotNull();
+ int actualSize = this.actual.size();
+ if (actualSize == expected) {
+ return this.myself;
+ }
+ throw this.assertionError(shouldHaveSize(this.actual, actualSize, expected));
+ }
+
+ /**
+ * Verifies that the number of key-value entry pairs in the {@link Multimap} is between the given boundaries
+ * (inclusive).
+ *
+ *
+ * @param lowerBoundary the lower boundary compared to which actual size should be greater than or equal to.
+ * @param higherBoundary the higher boundary compared to which actual size should be less than or equal to.
+ * @return {@code this} assertion object.
+ * @throws AssertionError if the actual size of the {@link Multimap} is not between the given boundaries.
+ */
+ public MultimapAssert hasSizeBetween(int lowerBoundary, int higherBoundary) {
+ this.isNotNull();
+ int actualSize = this.actual.size();
+ if (actualSize >= lowerBoundary && actualSize <= higherBoundary) {
+ return this.myself;
+ }
+ throw this.assertionError(shouldHaveSizeBetween(this.actual, actualSize, lowerBoundary, higherBoundary));
+ }
+
+ /**
+ * Verifies that the number of key-value entry pairs in the {@link Multimap} is greater than the specified boundary.
+ *
+ *
+ * @param boundary the size that the actual number of key-value pairs should exceed.
+ * @return {@code this} assertion object.
+ * @throws AssertionError if the actual size of the {@link Multimap} is not greater than the specified boundary.
+ */
+ public MultimapAssert hasSizeGreaterThan(int boundary) {
+ this.isNotNull();
+ int actualSize = this.actual.size();
+ if (actualSize > boundary) {
+ return this.myself;
+ }
+ throw this.assertionError(shouldHaveSizeGreaterThan(this.actual, actualSize, boundary));
+ }
+
+ /**
+ * Verifies that the number of key-value entry pairs in the {@link Multimap} is greater than or equal to the
+ * boundary.
+ *
+ *
+ * @param boundary the minimum size (inclusive) the {@link Multimap} should have.
+ * @return {@code this} assertion object for method chaining.
+ * @throws AssertionError if the actual size of the {@link Multimap} is less than the expected size.
+ */
+ public MultimapAssert hasSizeGreaterThanOrEqualTo(int boundary) {
+ this.isNotNull();
+ int actualSize = this.actual.size();
+ if (actualSize >= boundary) {
+ return this.myself;
+ }
+ throw this.assertionError(shouldHaveSizeGreaterThanOrEqualTo(this.actual, actualSize, boundary));
+ }
+
+ /**
+ * Verifies that the number of key-value entry pairs in the {@link Multimap} is less than the boundary.
+ *
+ *
+ * @param boundary the maximum size (exclusive) the {@link Multimap} should have.
+ * @return {@code this} assertion object.
+ * @throws AssertionError if the actual size of the {@link Multimap} is not less than the expected size.
+ */
+ public MultimapAssert hasSizeLessThan(int boundary) {
+ this.isNotNull();
+ int actualSize = this.actual.size();
+ if (actualSize < boundary) {
+ return this.myself;
+ }
+ throw this.assertionError(shouldHaveSizeLessThan(this.actual, actualSize, boundary));
+ }
+
+ /**
+ * Verifies that the number of key-value entry pairs in the {@link Multimap} is less than or equal to the boundary.
+ *
+ *
+ * @param boundary the maximum expected size of the {@link Multimap}.
+ * @return {@code this} assertion object for method chaining.
+ * @throws AssertionError if the actual size of the {@link Multimap} is greater than the expected size.
+ */
+ public MultimapAssert hasSizeLessThanOrEqualTo(int boundary) {
+ this.isNotNull();
+ int actualSize = this.actual.size();
+ if (actualSize <= boundary) {
+ return this.myself;
+ }
+ throw this.assertionError(shouldHaveSizeLessThanOrEqualTo(this.actual, actualSize, boundary));
+ }
+
+ /**
+ * Verifies that at least one value in the actual {@link Multimap} satisfies the given condition.
+ *
+ * @param valueCondition the condition to evaluate the values against; must not be null.
+ * @return this assertion object for method chaining.
+ * @throws NullPointerException if the provided condition is null.
+ * @throws AssertionError if none of the values in the {@link Multimap} satisfy the given condition.
+ */
+ public MultimapAssert hasValueSatisfying(Condition super VALUE> valueCondition) {
+ this.isNotNull();
+ requireNonNull(valueCondition, "The condition to evaluate should not be null");
+
+ if (this.actual.valuesView().anySatisfy(valueCondition::matches)) {
+ return this.myself;
+ }
+ throw this.assertionError(shouldContainValue(this.actual, valueCondition));
+ }
+
+ /**
+ * Verifies that the {@link Multimap} is empty.
+ *
+ *
+ * @throws AssertionError if the {@link Multimap} of values is not empty.
+ */
+ public void isEmpty() {
+ this.isNotNull();
+ if (!this.actual.isEmpty()) {
+ throw this.assertionError(shouldBeEmpty(this.actual));
+ }
+ }
+
+ /**
+ * Verifies that the {@link Multimap} is not empty.
+ *
+ *
+ * @throws AssertionError if the {@link Multimap} is either null or empty.
+ */
+ public void isNullOrEmpty() {
+ if (this.actual == null || this.actual.isEmpty()) {
+ return;
+ }
+ throw this.assertionError(shouldBeNullOrEmpty(this.actual));
+ }
+}
diff --git a/src/main/java/org/assertj/eclipse/collections/error/ShouldHaveDistinctSize.java b/src/main/java/org/assertj/eclipse/collections/error/ShouldHaveDistinctSize.java
new file mode 100644
index 0000000..1e9a28d
--- /dev/null
+++ b/src/main/java/org/assertj/eclipse/collections/error/ShouldHaveDistinctSize.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 2025-2025 the original author or authors.
+ */
+package org.assertj.eclipse.collections.error;
+
+import org.assertj.core.error.BasicErrorMessageFactory;
+import org.assertj.core.error.ErrorMessageFactory;
+
+import static java.lang.String.format;
+
+/**
+ * Creates an error message indicating that an assertion that verifies that a value has certain distinct size failed.
+ */
+public class ShouldHaveDistinctSize extends BasicErrorMessageFactory {
+ /**
+ * Creates a new {@code ShouldHaveDistinctSize}.
+ *
+ * @param actual the actual value in the failed assertion.
+ * @param actualSize the distinct size of {@code actual}.
+ * @param expectedSize the expected size.
+ * @return the created {@code ErrorMessageFactory}.
+ */
+ public static ErrorMessageFactory shouldHaveDistinctSize(Object actual, int actualSize, int expectedSize) {
+ return new ShouldHaveDistinctSize(actual, actualSize, expectedSize);
+ }
+
+ private ShouldHaveDistinctSize(Object actual, int actualSize, int expectedSize) {
+ super(format("%nExpected distinct size: %s but was: %s in:%n%s", expectedSize, actualSize, "%s"), actual);
+ }
+}
diff --git a/src/main/java/org/assertj/eclipse/collections/error/ShouldHaveDistinctSizeGreaterThan.java b/src/main/java/org/assertj/eclipse/collections/error/ShouldHaveDistinctSizeGreaterThan.java
new file mode 100644
index 0000000..bd41094
--- /dev/null
+++ b/src/main/java/org/assertj/eclipse/collections/error/ShouldHaveDistinctSizeGreaterThan.java
@@ -0,0 +1,43 @@
+/*
+ * 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 2025-2025 the original author or authors.
+ */
+package org.assertj.eclipse.collections.error;
+
+import static java.lang.String.format;
+
+import org.assertj.core.error.BasicErrorMessageFactory;
+import org.assertj.core.error.ErrorMessageFactory;
+
+/**
+ * Creates an error message indicating that an assertion that verifies a minimum distinct size failed.
+ */
+public class ShouldHaveDistinctSizeGreaterThan extends BasicErrorMessageFactory {
+ /**
+ * Creates a new {@link ShouldHaveDistinctSizeGreaterThan}.
+ *
+ * @param actual the actual value in the failed assertion.
+ * @param actualSize the size of {@code actual}.
+ * @param expectedMinSize the expected size.
+ * @return the created {@code ErrorMessageFactory}.
+ */
+ public static ErrorMessageFactory shouldHaveDistinctSizeGreaterThan(Object actual, int actualSize, int expectedMinSize) {
+ return new ShouldHaveDistinctSizeGreaterThan(actual, actualSize, expectedMinSize);
+ }
+
+ private ShouldHaveDistinctSizeGreaterThan(Object actual, int actualSize, int expectedSize) {
+ super(format("%n" +
+ "Expecting distinct size of:%n" +
+ " %%s%n" +
+ "to be greater than %s but was %s", expectedSize, actualSize),
+ actual);
+ }
+}
diff --git a/src/main/java/org/assertj/eclipse/collections/error/ShouldHaveDistinctSizeGreaterThanOrEqualTo.java b/src/main/java/org/assertj/eclipse/collections/error/ShouldHaveDistinctSizeGreaterThanOrEqualTo.java
new file mode 100644
index 0000000..207bda6
--- /dev/null
+++ b/src/main/java/org/assertj/eclipse/collections/error/ShouldHaveDistinctSizeGreaterThanOrEqualTo.java
@@ -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 2025-2025 the original author or authors.
+ */
+package org.assertj.eclipse.collections.error;
+
+import org.assertj.core.error.BasicErrorMessageFactory;
+import org.assertj.core.error.ErrorMessageFactory;
+
+import static java.lang.String.format;
+
+/**
+ * Creates an error message indicating that an assertion verifying that the distinct size of a value
+ * is greater than or equal to a specified minimum size has failed.
+ */
+public class ShouldHaveDistinctSizeGreaterThanOrEqualTo extends BasicErrorMessageFactory {
+ /**
+ * Creates a new {@link ShouldHaveDistinctSizeGreaterThanOrEqualTo}.
+ *
+ * @param actual the actual value in the failed assertion.
+ * @param actualSize the size of {@code actual}.
+ * @param expectedMinSize the expected size.
+ * @return the created {@code ErrorMessageFactory}.
+ */
+ public static ErrorMessageFactory shouldHaveDistinctSizeGreaterThanOrEqualTo(Object actual, int actualSize, int expectedMinSize) {
+ return new ShouldHaveDistinctSizeGreaterThanOrEqualTo(actual, actualSize, expectedMinSize);
+ }
+
+ private ShouldHaveDistinctSizeGreaterThanOrEqualTo(Object actual, int actualSize, int expectedSize) {
+ super(format("%n" +
+ "Expecting distinct size of:%n" +
+ " %%s%n" +
+ "to be greater than or equal to %s but was %s", expectedSize, actualSize),
+ actual);
+ }
+}
diff --git a/src/test/java/org/assertj/eclipse/collections/api/multimap/MultimapAssert_ContainsEntry_Test.java b/src/test/java/org/assertj/eclipse/collections/api/multimap/MultimapAssert_ContainsEntry_Test.java
new file mode 100644
index 0000000..9bf2ac7
--- /dev/null
+++ b/src/test/java/org/assertj/eclipse/collections/api/multimap/MultimapAssert_ContainsEntry_Test.java
@@ -0,0 +1,67 @@
+/*
+ * 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 2025-2025 the original author or authors.
+ */
+package org.assertj.eclipse.collections.api.multimap;
+
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.assertj.core.api.Assertions.assertThatNoException;
+import static org.eclipse.collections.impl.tuple.Tuples.pair;
+
+import org.assertj.eclipse.collections.api.SoftAssertions;
+import org.eclipse.collections.api.multimap.Multimap;
+import org.eclipse.collections.api.tuple.Pair;
+import org.eclipse.collections.impl.factory.Multimaps;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class MultimapAssert_ContainsEntry_Test {
+
+ @ParameterizedTest
+ @MethodSource("org.assertj.eclipse.collections.api.multimap.MultimapTestData#nonEmptyMultimaps")
+ void passes(Multimap actual) {
+ assertThatNoException().isThrownBy(() -> new MultimapAssert<>(actual).containsEntry("ENT", "Reed"));
+ }
+
+ @ParameterizedTest
+ @MethodSource("org.assertj.eclipse.collections.api.multimap.MultimapTestData#emptyMultimaps")
+ void failsEmpty(Multimap actual) {
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> new MultimapAssert<>(actual).containsEntry("ENT", "Reed"))
+ .withMessageContaining("Expecting")
+ .withMessageContaining("to contain")
+ .withMessageContaining("but could not find the following element(s)");
+ }
+
+ @Test
+ void failsNullMultimap() {
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> new MultimapAssert<>(null).containsEntry("ENT", "Reed"))
+ .withMessageContaining("Expecting actual not to be null");
+ }
+
+ @ParameterizedTest
+ @MethodSource("org.assertj.eclipse.collections.api.multimap.MultimapTestData#nonEmptyMultimaps")
+ void failsMissingEntry(Multimap actual) {
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> new MultimapAssert<>(actual).containsEntry("VOY", "Kes"))
+ .withMessageContaining("Expecting")
+ .withMessageContaining("to contain")
+ .withMessageContaining("but could not find the following element(s)");
+ }
+
+ @ParameterizedTest
+ @MethodSource("org.assertj.eclipse.collections.api.multimap.MultimapTestData#nonEmptyMultimaps")
+ void softAssertionPasses(Multimap actual) {
+ SoftAssertions.assertSoftly(softly -> softly.assertThat(actual).containsEntry("ENT", "Reed"));
+ }
+}
diff --git a/src/test/java/org/assertj/eclipse/collections/api/multimap/MultimapAssert_ContainsKeys_Test.java b/src/test/java/org/assertj/eclipse/collections/api/multimap/MultimapAssert_ContainsKeys_Test.java
new file mode 100644
index 0000000..7bf5aa8
--- /dev/null
+++ b/src/test/java/org/assertj/eclipse/collections/api/multimap/MultimapAssert_ContainsKeys_Test.java
@@ -0,0 +1,64 @@
+/*
+ * 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 2025-2025 the original author or authors.
+ */
+package org.assertj.eclipse.collections.api.multimap;
+
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.assertj.core.api.Assertions.assertThatNoException;
+
+import org.assertj.eclipse.collections.api.SoftAssertions;
+import org.eclipse.collections.api.multimap.Multimap;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class MultimapAssert_ContainsKeys_Test {
+
+ @ParameterizedTest
+ @MethodSource("org.assertj.eclipse.collections.api.multimap.MultimapTestData#nonEmptyMultimaps")
+ void passes(Multimap actual) {
+ assertThatNoException().isThrownBy(() -> new MultimapAssert<>(actual).containsKeys("TOS", "TNG", "DS9"));
+ }
+
+ @ParameterizedTest
+ @MethodSource("org.assertj.eclipse.collections.api.multimap.MultimapTestData#emptyMultimaps")
+ void failsEmpty(Multimap actual) {
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> new MultimapAssert<>(actual).containsKeys("TOS", "TNG", "DS9"))
+ .withMessageContaining("Expecting actual")
+ .withMessageContaining("{}")
+ .withMessageContaining("to contain keys");
+ }
+
+ @ParameterizedTest
+ @MethodSource("org.assertj.eclipse.collections.api.multimap.MultimapTestData#nonEmptyMultimaps")
+ void failsMissingKey(Multimap actual) {
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> new MultimapAssert<>(actual).containsKeys("DIS"))
+ .withMessageContaining("Expecting actual")
+ .withMessageContaining("to contain key")
+ .withMessageContaining("DIS");
+ }
+
+ @Test
+ void failsNullMultimap() {
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> new MultimapAssert<>(null).containsKeys("TOS", "TNG", "DS9"))
+ .withMessageContaining("Expecting actual not to be null");
+ }
+
+ @ParameterizedTest
+ @MethodSource("org.assertj.eclipse.collections.api.multimap.MultimapTestData#nonEmptyMultimaps")
+ void softAssertionPasses(Multimap actual) {
+ SoftAssertions.assertSoftly(softly -> softly.assertThat(actual).containsKeys("TOS", "TNG", "DS9"));
+ }
+}
diff --git a/src/test/java/org/assertj/eclipse/collections/api/multimap/MultimapAssert_ContainsOnly_Test.java b/src/test/java/org/assertj/eclipse/collections/api/multimap/MultimapAssert_ContainsOnly_Test.java
new file mode 100644
index 0000000..0e98b72
--- /dev/null
+++ b/src/test/java/org/assertj/eclipse/collections/api/multimap/MultimapAssert_ContainsOnly_Test.java
@@ -0,0 +1,185 @@
+/*
+ * 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 2025-2025 the original author or authors.
+ */
+package org.assertj.eclipse.collections.api.multimap;
+
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.assertj.core.api.Assertions.assertThatNoException;
+import static org.eclipse.collections.impl.tuple.Tuples.pair;
+
+import java.util.Map;
+
+import org.assertj.eclipse.collections.api.SoftAssertions;
+import org.eclipse.collections.api.multimap.Multimap;
+import org.eclipse.collections.api.tuple.Pair;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class MultimapAssert_ContainsOnly_Test {
+
+ @ParameterizedTest
+ @MethodSource("org.assertj.eclipse.collections.api.multimap.MultimapTestData#shipMultimaps")
+ void passesWithPairs(Multimap actual) {
+ Pair[] exactMatchPairs = new Pair[]{
+ pair("TNG", "Enterprise"),
+ pair("DS9", "Deep Space Nine"),
+ pair("DS9", "Defiant"),
+ pair("VOY", "Voyager")
+ };
+ assertThatNoException().isThrownBy(() -> new MultimapAssert<>(actual).containsOnly(exactMatchPairs));
+ }
+
+ @ParameterizedTest
+ @MethodSource("org.assertj.eclipse.collections.api.multimap.MultimapTestData#shipMultimaps")
+ void passesWithEntries(Multimap actual) {
+ Map.Entry[] exactMatchEntries = new Map.Entry[]{
+ pair("TNG", "Enterprise").toEntry(),
+ pair("DS9", "Deep Space Nine").toEntry(),
+ pair("DS9", "Defiant").toEntry(),
+ pair("VOY", "Voyager").toEntry()
+ };
+ assertThatNoException().isThrownBy(() -> new MultimapAssert<>(actual).containsOnly(exactMatchEntries));
+ }
+
+ @ParameterizedTest
+ @MethodSource("org.assertj.eclipse.collections.api.multimap.MultimapTestData#shipMultimaps")
+ void failsWhenAdditionalKeysOrValuesExistWithPair(Multimap actual) {
+ Pair[] partialMatchMissingPairs = new Pair[]{
+ pair("TNG", "Enterprise"),
+ pair("DS9", "Defiant"),
+ pair("VOY", "Voyager")
+ };
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> new MultimapAssert<>(actual).containsOnly(partialMatchMissingPairs))
+ .withMessageContaining("to contain only")
+ .withMessageContaining("but the following multimap entries were unexpected");
+ }
+
+ @ParameterizedTest
+ @MethodSource("org.assertj.eclipse.collections.api.multimap.MultimapTestData#shipMultimaps")
+ void failsWhenAdditionalKeysOrValuesExistWithEntry(Multimap actual) {
+ Map.Entry[] partialMatchMissingEntries = new Map.Entry[]{
+ pair("TNG", "Enterprise").toEntry(),
+ pair("DS9", "Defiant").toEntry(),
+ pair("VOY", "Voyager").toEntry()
+ };
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> new MultimapAssert<>(actual).containsOnly(partialMatchMissingEntries))
+ .withMessageContaining("to contain only")
+ .withMessageContaining("but the following multimap entries were unexpected");
+ }
+
+ @ParameterizedTest
+ @MethodSource("org.assertj.eclipse.collections.api.multimap.MultimapTestData#shipMultimaps")
+ void failsWhenEntryIsMissingWithPair(Multimap actual) {
+ Pair[] partialMatchExtraPairs = new Pair[]{
+ pair("TOS", "Enterprise"),
+ pair("TNG", "Enterprise"),
+ pair("DS9", "Deep Space Nine"),
+ pair("DS9", "Defiant"),
+ pair("VOY", "Voyager")
+ };
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> new MultimapAssert<>(actual).containsOnly(partialMatchExtraPairs))
+ .withMessageContaining("to contain only")
+ .withMessageContaining("but could not find the following multimap entries");
+ }
+
+ @ParameterizedTest
+ @MethodSource("org.assertj.eclipse.collections.api.multimap.MultimapTestData#shipMultimaps")
+ void failsWhenEntryIsMissingWithEntry(Multimap actual) {
+ Map.Entry[] partialMatchExtraEntries = new Map.Entry[]{
+ pair("TOS", "Enterprise").toEntry(),
+ pair("TNG", "Enterprise").toEntry(),
+ pair("DS9", "Deep Space Nine").toEntry(),
+ pair("DS9", "Defiant").toEntry(),
+ pair("VOY", "Voyager").toEntry()
+ };
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> new MultimapAssert<>(actual).containsOnly(partialMatchExtraEntries))
+ .withMessageContaining("to contain only")
+ .withMessageContaining("but could not find the following multimap entries");
+ }
+
+ @ParameterizedTest
+ @MethodSource("org.assertj.eclipse.collections.api.multimap.MultimapTestData#emptyMultimaps")
+ void failsForEmptyMultimapWithPair(Multimap actual) {
+ Pair[] exactMatchPairs = new Pair[]{
+ pair("TNG", "Enterprise"),
+ pair("DS9", "Deep Space Nine")
+ };
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> new MultimapAssert<>(actual).containsOnly(exactMatchPairs))
+ .withMessageContaining("to contain only")
+ .withMessageContaining("but could not find the following multimap entries");
+ }
+
+ @ParameterizedTest
+ @MethodSource("org.assertj.eclipse.collections.api.multimap.MultimapTestData#emptyMultimaps")
+ void failsForEmptyMultimapWithEntry(Multimap actual) {
+ Map.Entry[] exactMatchEntries = new Map.Entry[]{
+ pair("TNG", "Enterprise").toEntry(),
+ pair("DS9", "Deep Space Nine").toEntry()
+ };
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> new MultimapAssert<>(actual).containsOnly(exactMatchEntries))
+ .withMessageContaining("to contain only")
+ .withMessageContaining("but could not find the following multimap entries");
+ }
+
+ @Test
+ void failsForNullMultimapWithPair() {
+ Pair[] exactMatchPairs = new Pair[]{
+ pair("TNG", "Enterprise"),
+ pair("DS9", "Deep Space Nine")
+ };
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> new MultimapAssert<>(null).containsOnly(exactMatchPairs))
+ .withMessageContaining("Expecting actual not to be null");
+ }
+
+ @Test
+ void failsForNullMultimapWithEntry() {
+ Map.Entry[] exactMatchEntries = new Map.Entry[]{
+ pair("TNG", "Enterprise").toEntry(),
+ pair("DS9", "Deep Space Nine").toEntry()
+ };
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> new MultimapAssert<>(null).containsOnly(exactMatchEntries))
+ .withMessageContaining("Expecting actual not to be null");
+ }
+
+ @ParameterizedTest
+ @MethodSource("org.assertj.eclipse.collections.api.multimap.MultimapTestData#shipMultimaps")
+ void softAssertionPassesWithPairs(Multimap actual) {
+ Pair[] exactMatchPairs = new Pair[]{
+ pair("TNG", "Enterprise"),
+ pair("DS9", "Deep Space Nine"),
+ pair("DS9", "Defiant"),
+ pair("VOY", "Voyager")
+ };
+ SoftAssertions.assertSoftly(softly -> softly.assertThat(actual).containsOnly(exactMatchPairs));
+ }
+
+ @ParameterizedTest
+ @MethodSource("org.assertj.eclipse.collections.api.multimap.MultimapTestData#shipMultimaps")
+ void softAssertionPassesWithEntries(Multimap actual) {
+ Map.Entry[] exactMatchEntries = new Map.Entry[]{
+ pair("TNG", "Enterprise").toEntry(),
+ pair("DS9", "Deep Space Nine").toEntry(),
+ pair("DS9", "Defiant").toEntry(),
+ pair("VOY", "Voyager").toEntry()
+ };
+ SoftAssertions.assertSoftly(softly -> softly.assertThat(actual).containsOnly(exactMatchEntries));
+ }
+}
diff --git a/src/test/java/org/assertj/eclipse/collections/api/multimap/MultimapAssert_ContainsValues_Test.java b/src/test/java/org/assertj/eclipse/collections/api/multimap/MultimapAssert_ContainsValues_Test.java
new file mode 100644
index 0000000..9a50f29
--- /dev/null
+++ b/src/test/java/org/assertj/eclipse/collections/api/multimap/MultimapAssert_ContainsValues_Test.java
@@ -0,0 +1,64 @@
+/*
+ * 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 2025-2025 the original author or authors.
+ */
+package org.assertj.eclipse.collections.api.multimap;
+
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.assertj.core.api.Assertions.assertThatNoException;
+
+import org.assertj.eclipse.collections.api.SoftAssertions;
+import org.eclipse.collections.api.multimap.Multimap;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class MultimapAssert_ContainsValues_Test {
+
+ @ParameterizedTest
+ @MethodSource("org.assertj.eclipse.collections.api.multimap.MultimapTestData#nonEmptyMultimaps")
+ void passes(Multimap actual) {
+ assertThatNoException().isThrownBy(() -> new MultimapAssert<>(actual).containsValues("Kirk", "Picard", "Sisko", "Janeway", "Archer"));
+ }
+
+ @ParameterizedTest
+ @MethodSource("org.assertj.eclipse.collections.api.multimap.MultimapTestData#emptyMultimaps")
+ void failsEmpty(Multimap actual) {
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> new MultimapAssert<>(actual).containsValues("Kirk", "Picard", "Sisko", "Janeway", "Archer"))
+ .withMessageContaining("Expecting actual")
+ .withMessageContaining("{}")
+ .withMessageContaining("to contain values");
+ }
+
+ @ParameterizedTest
+ @MethodSource("org.assertj.eclipse.collections.api.multimap.MultimapTestData#nonEmptyMultimaps")
+ void failsMissingValue(Multimap actual) {
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> new MultimapAssert<>(actual).containsValues("Kes"))
+ .withMessageContaining("Expecting actual")
+ .withMessageContaining("to contain value")
+ .withMessageContaining("Kes");
+ }
+
+ @Test
+ void failsNullMultimap() {
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> new MultimapAssert<>(null).containsValues("Kirk", "Picard", "Sisko", "Janeway", "Archer"))
+ .withMessageContaining("Expecting actual not to be null");
+ }
+
+ @ParameterizedTest
+ @MethodSource("org.assertj.eclipse.collections.api.multimap.MultimapTestData#nonEmptyMultimaps")
+ void softAssertionPasses(Multimap actual) {
+ SoftAssertions.assertSoftly(softly -> softly.assertThat(actual).containsValues("Kirk", "Picard", "Sisko", "Janeway", "Archer"));
+ }
+}
diff --git a/src/test/java/org/assertj/eclipse/collections/api/multimap/MultimapAssert_Contains_Test.java b/src/test/java/org/assertj/eclipse/collections/api/multimap/MultimapAssert_Contains_Test.java
new file mode 100644
index 0000000..789cdd8
--- /dev/null
+++ b/src/test/java/org/assertj/eclipse/collections/api/multimap/MultimapAssert_Contains_Test.java
@@ -0,0 +1,123 @@
+/*
+ * 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 2025-2025 the original author or authors.
+ */
+package org.assertj.eclipse.collections.api.multimap;
+
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.assertj.core.api.Assertions.assertThatNoException;
+import static org.eclipse.collections.impl.tuple.Tuples.pair;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
+
+import java.util.Map;
+import java.util.stream.Stream;
+
+import org.assertj.eclipse.collections.api.SoftAssertions;
+import org.eclipse.collections.api.multimap.Multimap;
+import org.eclipse.collections.api.tuple.Pair;
+import org.eclipse.collections.impl.tuple.Tuples;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class MultimapAssert_Contains_Test {
+
+ @ParameterizedTest
+ @MethodSource("successfulPairTestCases")
+ void containsPairsPasses(Multimap actual, Pair[] expectedPairs) {
+ assertThatNoException().isThrownBy(() -> new MultimapAssert<>(actual).contains(expectedPairs));
+ }
+
+ @ParameterizedTest
+ @MethodSource("missingPairTestCases")
+ void containsPairsFails(Multimap actual, Pair missingPair) {
+ assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
+ new MultimapAssert<>(actual).contains(missingPair))
+ .withMessageContaining("Expecting")
+ .withMessageContaining("but could not find the following element(s)")
+ .withMessageContaining(missingPair.toString());
+ }
+
+ @Test
+ void nullActualWithPair() {
+ assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
+ new MultimapAssert<>(null).contains(pair("TOS", "McCoy")))
+ .withMessageContaining("Expecting actual not to be null");
+ }
+
+ @ParameterizedTest
+ @MethodSource("successfulPairTestCases")
+ void softAssertionsWithPairPasses(Multimap actual, Pair[] expectedPairs) {
+ SoftAssertions.assertSoftly(softly -> softly.assertThat(actual).contains(expectedPairs));
+ }
+
+ @ParameterizedTest
+ @MethodSource("successfulMapEntryTestCases")
+ void containsEntriesPasses(Multimap actual, Map.Entry[] expectedEntries) {
+ assertThatNoException().isThrownBy(() -> new MultimapAssert<>(actual).contains(expectedEntries));
+ }
+
+ @ParameterizedTest
+ @MethodSource("missingMapEntryTestCases")
+ void containsEntriesFails(Multimap actual, Map.Entry missingEntry) {
+ assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
+ new MultimapAssert<>(actual).contains(missingEntry))
+ .withMessageContaining("Expecting")
+ .withMessageContaining("but could not find the following element(s)")
+ .withMessageContaining(Tuples.pairFrom(missingEntry).toString());
+ }
+
+ @Test
+ void nullActualWithEntry() {
+ assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
+ new MultimapAssert<>(null).contains((Map.Entry) pair("TOS", "McCoy").toEntry()))
+ .withMessageContaining("Expecting actual not to be null");
+ }
+
+ @ParameterizedTest
+ @MethodSource("successfulMapEntryTestCases")
+ void softAssertionsWithEntryPasses(Multimap actual, Map.Entry[] expectedEntries) {
+ SoftAssertions.assertSoftly(softly -> softly.assertThat(actual).contains(expectedEntries));
+ }
+
+ private static Stream successfulPairTestCases() {
+ return Stream.of(
+ arguments(MultimapTestData.mutableBagMultimap(), new Pair[]{pair("TOS", "McCoy"), pair("TNG", "Crusher")}),
+ arguments(MultimapTestData.mutableListMultimap(), new Pair[]{pair("TOS", "McCoy"), pair("TNG", "Crusher")}),
+ arguments(MultimapTestData.mutableSetMultimap(), new Pair[]{pair("TOS", "McCoy"), pair("TNG", "Crusher")})
+ );
+ }
+
+ private static Stream successfulMapEntryTestCases() {
+ return Stream.of(
+ arguments(MultimapTestData.mutableBagMultimap(), new Map.Entry[]{pair("TOS", "McCoy").toEntry(), pair("TNG", "Crusher").toEntry()}),
+ arguments(MultimapTestData.mutableListMultimap(), new Map.Entry[]{pair("TOS", "McCoy").toEntry(), pair("TNG", "Crusher").toEntry()}),
+ arguments(MultimapTestData.mutableSetMultimap(), new Map.Entry[]{pair("TOS", "McCoy").toEntry(), pair("TNG", "Crusher").toEntry()})
+ );
+ }
+
+ private static Stream missingPairTestCases() {
+ return Stream.of(
+ arguments(MultimapTestData.mutableBagMultimap(), pair("VOY", "Kes")),
+ arguments(MultimapTestData.mutableListMultimap(), pair("VOY", "Kes")),
+ arguments(MultimapTestData.mutableSetMultimap(), pair("VOY", "Kes"))
+ );
+ }
+
+ private static Stream missingMapEntryTestCases() {
+ return Stream.of(
+ arguments(MultimapTestData.mutableBagMultimap(), pair("VOY", "Kes").toEntry()),
+ arguments(MultimapTestData.mutableListMultimap(), pair("VOY", "Kes").toEntry()),
+ arguments(MultimapTestData.mutableSetMultimap(), pair("VOY", "Kes").toEntry())
+ );
+ }
+}
diff --git a/src/test/java/org/assertj/eclipse/collections/api/multimap/MultimapAssert_HasDistinctSizeGreaterThan_Test.java b/src/test/java/org/assertj/eclipse/collections/api/multimap/MultimapAssert_HasDistinctSizeGreaterThan_Test.java
new file mode 100644
index 0000000..deda665
--- /dev/null
+++ b/src/test/java/org/assertj/eclipse/collections/api/multimap/MultimapAssert_HasDistinctSizeGreaterThan_Test.java
@@ -0,0 +1,73 @@
+/*
+ * 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 2025-2025 the original author or authors.
+ */
+package org.assertj.eclipse.collections.api.multimap;
+
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.assertj.core.api.Assertions.assertThatNoException;
+
+import org.assertj.eclipse.collections.api.SoftAssertions;
+import org.eclipse.collections.api.multimap.Multimap;
+import org.eclipse.collections.impl.factory.Multimaps;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class MultimapAssert_HasDistinctSizeGreaterThan_Test {
+
+ @ParameterizedTest
+ @MethodSource("org.assertj.eclipse.collections.api.multimap.MultimapTestData#distinctSizeLowerBoundaryTestData")
+ void passesGreaterThan(Multimap actual, int lowerBoundary) {
+ assertThatNoException().isThrownBy(() -> new MultimapAssert<>(actual).hasDistinctSizeGreaterThan(lowerBoundary));
+ }
+
+ @ParameterizedTest
+ @MethodSource("org.assertj.eclipse.collections.api.multimap.MultimapTestData#distinctSizeUpperBoundaryTestData")
+ void failsLesser(Multimap actual, int upperBoundary) {
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> new MultimapAssert<>(actual).hasDistinctSizeGreaterThan(upperBoundary))
+ .withMessageContaining("Expecting distinct size of")
+ .withMessageContaining(String.format("to be greater than %s but was %s", upperBoundary, actual.sizeDistinct()));
+ }
+
+ @ParameterizedTest
+ @MethodSource("org.assertj.eclipse.collections.api.multimap.MultimapTestData#distinctSizeEqualsTestData")
+ void failsEquals(Multimap actual, int equalsBoundary) {
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> new MultimapAssert<>(actual).hasDistinctSizeGreaterThan(equalsBoundary))
+ .withMessageContaining("Expecting distinct size of")
+ .withMessageContaining(String.format("to be greater than %s but was %s", equalsBoundary, actual.sizeDistinct()));
+ }
+
+ @Test
+ void failsNullMultimap() {
+ int lowerBoundary = 2; // Using the same value as in distinctSizeLowerBoundaryTestData
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> new MultimapAssert<>(null).hasDistinctSizeGreaterThan(lowerBoundary))
+ .withMessageContaining("Expecting actual not to be null");
+ }
+
+ @ParameterizedTest
+ @MethodSource("org.assertj.eclipse.collections.api.multimap.MultimapTestData#emptyMultimaps")
+ void failsEmptyMultimap(Multimap actual) {
+ int lowerBoundary = 2; // Using the same value as in distinctSizeLowerBoundaryTestData
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> new MultimapAssert<>(actual).hasDistinctSizeGreaterThan(lowerBoundary))
+ .withMessageContaining(String.format("to be greater than %s but was 0", lowerBoundary));
+ }
+
+ @ParameterizedTest
+ @MethodSource("org.assertj.eclipse.collections.api.multimap.MultimapTestData#distinctSizeLowerBoundaryTestData")
+ void softAssertionPasses(Multimap actual, int lowerBoundary) {
+ SoftAssertions.assertSoftly(softly -> softly.assertThat(actual).hasDistinctSizeGreaterThan(lowerBoundary));
+ }
+}
diff --git a/src/test/java/org/assertj/eclipse/collections/api/multimap/MultimapAssert_HasDistinctSize_Test.java b/src/test/java/org/assertj/eclipse/collections/api/multimap/MultimapAssert_HasDistinctSize_Test.java
new file mode 100644
index 0000000..2769111
--- /dev/null
+++ b/src/test/java/org/assertj/eclipse/collections/api/multimap/MultimapAssert_HasDistinctSize_Test.java
@@ -0,0 +1,53 @@
+/*
+ * 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 2025-2025 the original author or authors.
+ */
+package org.assertj.eclipse.collections.api.multimap;
+
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.assertj.core.api.Assertions.assertThatNoException;
+
+import org.assertj.eclipse.collections.api.SoftAssertions;
+import org.eclipse.collections.api.multimap.Multimap;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class MultimapAssert_HasDistinctSize_Test {
+
+ @ParameterizedTest
+ @MethodSource("org.assertj.eclipse.collections.api.multimap.MultimapTestData#distinctSizeEqualsTestData")
+ void passes(Multimap actual, int expectedSize) {
+ assertThatNoException().isThrownBy(() -> new MultimapAssert<>(actual).hasDistinctSize(expectedSize));
+ }
+
+ @ParameterizedTest
+ @MethodSource("org.assertj.eclipse.collections.api.multimap.MultimapTestData#emptyMultimapsWithExpectedDistinctSize")
+ void failsEmpty(Multimap actual, int expectedSize) {
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> new MultimapAssert<>(actual).hasDistinctSize(expectedSize))
+ .withMessageContaining(String.format("Expected distinct size: %s but was: 0", expectedSize));
+ }
+
+ @Test
+ void failsNullMultimap() {
+ int expectedSize = 5; // Using the same value as in distinctSizeEqualsTestData
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> new MultimapAssert<>(null).hasDistinctSize(expectedSize))
+ .withMessageContaining("Expecting actual not to be null");
+ }
+
+ @ParameterizedTest
+ @MethodSource("org.assertj.eclipse.collections.api.multimap.MultimapTestData#distinctSizeEqualsTestData")
+ void softAssertionPasses(Multimap actual, int expectedSize) {
+ SoftAssertions.assertSoftly(softly -> softly.assertThat(actual).hasDistinctSize(expectedSize));
+ }
+}
diff --git a/src/test/java/org/assertj/eclipse/collections/api/multimap/MultimapAssert_HasKeySatisfying_Test.java b/src/test/java/org/assertj/eclipse/collections/api/multimap/MultimapAssert_HasKeySatisfying_Test.java
new file mode 100644
index 0000000..68be703
--- /dev/null
+++ b/src/test/java/org/assertj/eclipse/collections/api/multimap/MultimapAssert_HasKeySatisfying_Test.java
@@ -0,0 +1,56 @@
+/*
+ * 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 2025-2025 the original author or authors.
+ */
+package org.assertj.eclipse.collections.api.multimap;
+
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.assertj.core.api.Assertions.assertThatNoException;
+
+import org.assertj.core.api.Condition;
+import org.assertj.eclipse.collections.api.SoftAssertions;
+import org.eclipse.collections.api.multimap.Multimap;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class MultimapAssert_HasKeySatisfying_Test {
+
+ private static final Condition