diff --git a/libraries-testing/pom.xml b/libraries-testing/pom.xml
index 6d7e0c01e1..d7b4a88369 100644
--- a/libraries-testing/pom.xml
+++ b/libraries-testing/pom.xml
@@ -84,6 +84,12 @@
jsonassert
${jsonassert.version}
+
+ uk.org.webcompere
+ model-assert
+ ${modelassert.version}
+ test
+
org.awaitility
awaitility
@@ -209,6 +215,7 @@
1.8
3.8.1
0.14.1
+ 1.0.0
\ No newline at end of file
diff --git a/libraries-testing/src/test/java/com/baeldung/modelassert/ModelAssertUnitTest.java b/libraries-testing/src/test/java/com/baeldung/modelassert/ModelAssertUnitTest.java
new file mode 100644
index 0000000000..b32afdb6d0
--- /dev/null
+++ b/libraries-testing/src/test/java/com/baeldung/modelassert/ModelAssertUnitTest.java
@@ -0,0 +1,289 @@
+package com.baeldung.modelassert;
+
+import org.hamcrest.Matcher;
+import org.hamcrest.MatcherAssert;
+import org.junit.jupiter.api.Test;
+import org.opentest4j.AssertionFailedError;
+import uk.org.webcompere.modelassert.json.dsl.nodespecific.tree.WhereDsl;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static uk.org.webcompere.modelassert.json.JsonAssertions.*;
+import static uk.org.webcompere.modelassert.json.PathWildCard.ANY;
+import static uk.org.webcompere.modelassert.json.PathWildCard.ANY_SUBTREE;
+import static uk.org.webcompere.modelassert.json.Patterns.GUID_PATTERN;
+
+class ModelAssertUnitTest {
+ private static final String ACTUAL_JSON = "{" +
+ "\"name\": \"Baeldung\"," +
+ "\"isOnline\": true," +
+ "\"topics\": [ \"Java\", \"Spring\", \"Kotlin\", \"Scala\", \"Linux\" ]" +
+ "}";
+
+ private static final Path PATH_TO_EXPECTED = Paths.get("src", "test", "resources", "modelassert", "baeldung.json");
+
+ public interface DataService {
+
+ boolean isUserLoggedIn(String userDetails);
+ }
+
+ private DataService mockDataService = mock(DataService.class);
+
+ @Test
+ void givenJson_thenNameIsBaeldung() {
+ assertJson(ACTUAL_JSON)
+ .at("/name").isText("Baeldung");
+ }
+
+ @Test
+ void givenJson_thenSecondTopicIsSpring() {
+ assertJson(ACTUAL_JSON)
+ .at("/topics/1").isText("Spring");
+ }
+
+ @Test
+ void givenJson_thenCanMakeMultipleAssertions() {
+ assertJson(ACTUAL_JSON)
+ .at("/name").isText("Baeldung")
+ .at("/topics/1").isText("Spring");
+ }
+
+ @Test
+ void givenCompareByString_thenFailsOnFormatting() throws IOException {
+ String expected = String.join("\n", Files.readAllLines(PATH_TO_EXPECTED));
+
+ assertThatThrownBy(() -> assertThat(ACTUAL_JSON).isEqualTo(expected))
+ .isInstanceOf(AssertionFailedError.class);
+ }
+
+ @Test
+ void givenTwoIdenticalTreesInDifferentFormat_thenPassesWithIsEqualTo() {
+ assertJson(ACTUAL_JSON)
+ .isEqualTo(PATH_TO_EXPECTED);
+ }
+
+ @Test
+ void givenMap_thenCanCompareToYaml() {
+ Map map = new HashMap<>();
+ map.put("name", "baeldung");
+
+ assertJson(map)
+ .isEqualToYaml("name: baeldung");
+ }
+
+ @Test
+ void givenYaml_thenCanCompareToMap() {
+ Map map = new HashMap<>();
+ map.put("name", "baeldung");
+
+ assertYaml("name: baeldung")
+ .isEqualTo(map);
+ }
+
+ @Test
+ void canProduceHamcrestMatcher() {
+ Matcher matcher = json().at("/name").hasValue("Baeldung");
+
+ MatcherAssert.assertThat(ACTUAL_JSON, matcher);
+ }
+
+ @Test
+ void givenJson_thenCanAssertWithMatcherAssert() {
+
+ MatcherAssert.assertThat(ACTUAL_JSON, json()
+ .at("/name").hasValue("Baeldung")
+ .at("/topics/1").isText("Spring"));
+ }
+
+ @Test
+ void givenUserIsOnline_thenIsLoggedIn() {
+ given(mockDataService.isUserLoggedIn(argThat(json()
+ .at("/isOnline").isTrue()
+ .toArgumentMatcher())))
+ .willReturn(true);
+
+ assertThat(mockDataService.isUserLoggedIn(ACTUAL_JSON))
+ .isTrue();
+
+ verify(mockDataService)
+ .isUserLoggedIn(argThat(json()
+ .at("/name").isText("Baeldung")
+ .toArgumentMatcher()));
+ }
+
+ @Test
+ void givenDocument_canMakeSeveralRootNodeAssertions() {
+ assertJson(ACTUAL_JSON)
+ .isNotNull()
+ .isNotNumber()
+ .isObject()
+ .containsKey("name");
+ }
+
+ @Test
+ void givenDocument_canAssertArray() {
+ assertJson(ACTUAL_JSON)
+ .at("/topics").hasSize(5);
+ }
+
+ @Test
+ void givenDocument_canAssertBooleanByType() {
+ assertJson(ACTUAL_JSON)
+ .at("/isOnline").booleanNode().isTrue();
+ }
+
+ @Test
+ void givenTextNode_canAssertContents() {
+ assertJson(ACTUAL_JSON)
+ .at("/name").textContains("ael");
+ }
+
+ @Test
+ void givenTextNode_canMatchWithRegex() {
+ assertJson(ACTUAL_JSON)
+ .at("/name").matches("[A-Z].+");
+ }
+
+ @Test
+ void givenNumberNode_canTestRange() {
+ assertJson("{count: 12}")
+ .at("/count").isBetween(1, 25);
+ }
+
+ @Test
+ void givenNumberNode_canTestDouble() {
+ assertJson("{height: 6.3}")
+ .at("/height").isGreaterThanDouble(6.0);
+ }
+
+ @Test
+ void givenNumberNode_canTestDoubleEquals() {
+ assertJson("{height: 6.3}")
+ .at("/height").isNumberEqualTo(6.3);
+ }
+
+ @Test
+ void givenArrayNode_canTestContains() {
+ assertJson(ACTUAL_JSON)
+ .at("/topics").isArrayContaining("Scala", "Spring");
+ }
+
+ @Test
+ void givenArrayNode_canTestContainsExactlyInAnyOrder() {
+ assertJson(ACTUAL_JSON)
+ .at("/topics").isArrayContainingExactlyInAnyOrder("Scala", "Spring", "Java", "Linux", "Kotlin");
+ }
+
+ @Test
+ void givenArrayNode_canTestContainsExactly() {
+ assertJson(ACTUAL_JSON)
+ .at("/topics").isArrayContainingExactly("Java", "Spring", "Kotlin", "Scala", "Linux");
+ }
+
+ @Test
+ void givenArrayNode_thenCanAssertBySubtree() {
+ assertJson(ACTUAL_JSON)
+ .at("/topics").isEqualTo("[ \"Java\", \"Spring\", \"Kotlin\", \"Scala\", \"Linux\" ]");
+ }
+
+ @Test
+ void givenKeyOrderIsDifferent_canStillBeEqual() {
+ String actualJson = "{a:{d:3, c:2, b:1}}";
+ String expectedJson = "{a:{b:1, c:2, d:3}}";
+
+ assertJson(actualJson)
+ .where().keysInAnyOrder()
+ .isEqualTo(expectedJson);
+ }
+
+ @Test
+ void givenKeyOrderIsDifferentInA_canStillBeEqual() {
+ String actualJson = "{a:{d:3, c:2, b:1}}";
+ String expectedJson = "{a:{b:1, c:2, d:3}}";
+
+ assertJson(actualJson)
+ .where()
+ .at("/a").keysInAnyOrder()
+ .isEqualTo(expectedJson);
+ }
+
+ @Test
+ void givenElementOrderIsDifferent_canStillBeEqual() {
+ String actualJson = "{a:[1, 2, 3, 4, 5]}";
+ String expectedJson = "{a:[5, 4, 3, 2, 1]}";
+
+ assertJson(actualJson)
+ .where().arrayInAnyOrder()
+ .isEqualTo(expectedJson);
+ }
+
+ @Test
+ void givenActualHasExtraValue_thenIgnoreIt() {
+ String actualJson = "{user:{name: \"Baeldung\", url:\"http://www.baeldung.com\"}}";
+ String expectedJson = "{user:{name: \"Baeldung\"}}";
+
+ assertJson(actualJson)
+ .where().at("/user/url").isIgnored()
+ .isEqualTo(expectedJson);
+ }
+
+ @Test
+ void givenActualHasIdGUIDS_thenIgnoreThem() {
+ String actualJson = "{user:{credentials:[" +
+ "{id:\"a7dc2567-3340-4a3b-b1ab-9ce1778f265d\",role:\"Admin\"}," +
+ "{id:\"09da84ba-19c2-4674-974f-fd5afff3a0e5\",role:\"Sales\"}]}}";
+ String expectedJson = "{user:{credentials:" +
+ "[{id:\"???\",role:\"Admin\"}," +
+ "{id:\"???\",role:\"Sales\"}]}}";
+
+ assertJson(actualJson)
+ .where()
+ .path("user","credentials", ANY, "id").isIgnored()
+ .isEqualTo(expectedJson);
+ }
+
+ @Test
+ void givenActualHasIdGUIDS_thenMatchThemByRegexAndSubtree() {
+ String actualJson = "{user:{credentials:[" +
+ "{id:\"a7dc2567-3340-4a3b-b1ab-9ce1778f265d\",role:\"Admin\"}," +
+ "{id:\"09da84ba-19c2-4674-974f-fd5afff3a0e5\",role:\"Sales\"}]}}";
+ String expectedJson = "{user:{credentials:" +
+ "[{id:\"???\",role:\"Admin\"}," +
+ "{id:\"???\",role:\"Sales\"}]}}";
+
+ assertJson(actualJson)
+ .where()
+ .path(ANY_SUBTREE, "id").matches(GUID_PATTERN)
+ .isEqualTo(expectedJson);
+ }
+
+ @Test
+ void givenActualHasIdGUIDS_thenMatchThemByRegexAndSubtreeUsingStandardPattern() {
+ String actualJson = "{user:{credentials:[" +
+ "{id:\"a7dc2567-3340-4a3b-b1ab-9ce1778f265d\",role:\"Admin\"}," +
+ "{id:\"09da84ba-19c2-4674-974f-fd5afff3a0e5\",role:\"Sales\"}]}}";
+ String expectedJson = "{user:{credentials:" +
+ "[{id:\"???\",role:\"Admin\"}," +
+ "{id:\"???\",role:\"Sales\"}]}}";
+
+ assertJson(actualJson)
+ .where()
+ .configuredBy(where -> idsAreGuids(where))
+ .isEqualTo(expectedJson);
+ }
+
+ private static WhereDsl idsAreGuids(WhereDsl where) {
+ return where.path(ANY_SUBTREE, "id").matches(GUID_PATTERN);
+ }
+}
diff --git a/libraries-testing/src/test/resources/modelassert/baeldung.json b/libraries-testing/src/test/resources/modelassert/baeldung.json
new file mode 100644
index 0000000000..2eb8ed3859
--- /dev/null
+++ b/libraries-testing/src/test/resources/modelassert/baeldung.json
@@ -0,0 +1,5 @@
+{
+ "name": "Baeldung",
+ "isOnline": true,
+ "topics": [ "Java", "Spring", "Kotlin", "Scala", "Linux" ]
+}