From 0109bf6858614f16d4c0d5dcdd69c99d38252426 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 24 Oct 2017 12:02:00 +0200 Subject: [PATCH] DATAMONGO-1809 - Introduce AssertJ assertions for Document. Original pull request: #508. --- .../core/convert/UpdateMapperUnitTests.java | 153 +++---- .../data/mongodb/test/util/AssertErrors.java | 80 ++++ .../data/mongodb/test/util/Assertions.java | 41 ++ .../data/mongodb/test/util/Conditions.java | 64 --- .../mongodb/test/util/DocumentAssert.java | 384 ++++++++++++++++++ 5 files changed, 570 insertions(+), 152 deletions(-) create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/AssertErrors.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/Assertions.java delete mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/Conditions.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/DocumentAssert.java diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java index 6e1de7f8c..b7ac574f9 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java @@ -15,11 +15,9 @@ */ package org.springframework.data.mongodb.core.convert; -import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; import static org.springframework.data.mongodb.core.DocumentTestUtils.*; -import static org.springframework.data.mongodb.test.util.Conditions.*; -import static org.springframework.data.mongodb.test.util.IsBsonObject.*; +import static org.springframework.data.mongodb.test.util.Assertions.*; import lombok.AllArgsConstructor; import lombok.NoArgsConstructor; @@ -32,8 +30,6 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import org.bson.Document; -import org.bson.conversions.Bson; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -191,8 +187,7 @@ public class UpdateMapperUnitTests { assertThat(document.get("aliased.$.value")).isEqualTo("foo"); Document someObject = getAsDocument(document, "aliased.$.someObject"); - assertThat(someObject).isNotNull(); - assertThat(someObject.get("value")).isEqualTo("bubu"); + assertThat(someObject).isNotNull().containsEntry("value", "bubu"); assertTypeHint(someObject, ConcreteChildClass.class); } @@ -263,8 +258,8 @@ public class UpdateMapperUnitTests { Document mappedObject = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(Object.class)); Document push = getAsDocument(mappedObject, "$push"); - assertThat(getAsDocument(push, "category").containsKey("$each")).isTrue(); - assertThat(getAsDocument(push, "type").containsKey("$each")).isTrue(); + assertThat(getAsDocument(push, "category")).containsKey("$each"); + assertThat(getAsDocument(push, "type")).containsKey("$each"); } @Test // DATAMONGO-943 @@ -279,7 +274,7 @@ public class UpdateMapperUnitTests { assertThat(key.containsKey("$position")).isTrue(); assertThat(key.get("$position")).isEqualTo(2); - assertThat(getAsDocument(push, "key").containsKey("$each")).isTrue(); + assertThat(getAsDocument(push, "key")).containsKey("$each"); } @Test // DATAMONGO-943 @@ -294,7 +289,7 @@ public class UpdateMapperUnitTests { assertThat(key.containsKey("$position")).isTrue(); assertThat(key.get("$position")).isEqualTo(0); - assertThat(getAsDocument(push, "key").containsKey("$each")).isTrue(); + assertThat(getAsDocument(push, "key")).containsKey("$each"); } @Test // DATAMONGO-943 @@ -307,8 +302,8 @@ public class UpdateMapperUnitTests { Document push = getAsDocument(mappedObject, "$push"); Document key = getAsDocument(push, "key"); - assertThat(key.containsKey("$position")).isFalse(); - assertThat(getAsDocument(push, "key").containsKey("$each")).isTrue(); + assertThat(key).doesNotContainKey("$position"); + assertThat(getAsDocument(push, "key")).containsKey("$each"); } @Test // DATAMONGO-943 @@ -321,8 +316,8 @@ public class UpdateMapperUnitTests { Document push = getAsDocument(mappedObject, "$push"); Document key = getAsDocument(push, "key"); - assertThat(key.containsKey("$position")).isFalse(); - assertThat(getAsDocument(push, "key").containsKey("$each")).isTrue(); + assertThat(key).doesNotContainKey("$position"); + assertThat(getAsDocument(push, "key")).containsKey("$each"); } @Test // DATAMONGO-832 @@ -335,9 +330,8 @@ public class UpdateMapperUnitTests { Document push = getAsDocument(mappedObject, "$push"); Document key = getAsDocument(push, "key"); - assertThat(key.containsKey("$slice")).isTrue(); - assertThat(key.get("$slice")).isEqualTo(5); - assertThat(key.containsKey("$each")).isTrue(); + assertThat(key).containsKey("$slice").containsEntry("$slice", 5); + assertThat(key).containsKey("$each"); } @Test // DATAMONGO-832 @@ -351,15 +345,13 @@ public class UpdateMapperUnitTests { Document push = getAsDocument(mappedObject, "$push"); Document key = getAsDocument(push, "key"); - assertThat(key.containsKey("$slice")).isTrue(); - assertThat(key.get("$slice")).isEqualTo(5); + assertThat(key).containsKey("$slice").containsEntry("$slice", 5); assertThat(key.containsKey("$each")).isTrue(); Document key2 = getAsDocument(push, "key-2"); - assertThat(key2.containsKey("$slice")).isTrue(); - assertThat(key2.get("$slice")).isEqualTo(-2); - assertThat(key2.containsKey("$each")).isTrue(); + assertThat(key2).containsKey("$slice").containsEntry("$slice", -2); + assertThat(key2).containsKey("$each"); } @Test // DATAMONGO-1141 @@ -373,9 +365,9 @@ public class UpdateMapperUnitTests { Document push = getAsDocument(mappedObject, "$push"); Document key = getAsDocument(push, "scores"); - assertThat(key.containsKey("$sort")).isTrue(); - assertThat(key.get("$sort")).isEqualTo(-1); - assertThat(key.containsKey("$each")).isTrue(); + assertThat(key).containsKey("$sort"); + assertThat(key).containsEntry("$sort", -1); + assertThat(key).containsKey("$each"); } @Test // DATAMONGO-1141 @@ -391,9 +383,9 @@ public class UpdateMapperUnitTests { Document push = getAsDocument(mappedObject, "$push"); Document key = getAsDocument(push, "list"); - assertThat(key.containsKey("$sort")).isTrue(); + assertThat(key).containsKey("$sort"); assertThat(key.get("$sort")).isEqualTo(new Document("renamed-value", 1).append("field", 1)); - assertThat(key.containsKey("$each")).isTrue(); + assertThat(key).containsKey("$each"); } @Test // DATAMONGO-1141 @@ -407,13 +399,13 @@ public class UpdateMapperUnitTests { Document push = getAsDocument(mappedObject, "$push"); Document key1 = getAsDocument(push, "authors"); - assertThat(key1.containsKey("$sort")).isTrue(); - assertThat(key1.get("$sort")).isEqualTo(1); - assertThat(key1.containsKey("$each")).isTrue(); + assertThat(key1).containsKey("$sort"); + assertThat(key1).containsEntry("$sort", 1); + assertThat(key1).containsKey("$each"); Document key2 = getAsDocument(push, "chapters"); - assertThat(key2.containsKey("$sort")).isTrue(); + assertThat(key2).containsKey("$sort"); assertThat(key2.get("$sort")).isEqualTo(new Document("order", 1)); assertThat(key2.containsKey("$each")).isTrue(); } @@ -615,10 +607,8 @@ public class UpdateMapperUnitTests { Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(DocumentWithNestedCollection.class)); - assertThat((Bson) mappedUpdate) - .is(matchedBy(isBsonObject().notContaining("$addToSet.nestedDocs.$each.[0]._class"))); - assertThat((Bson) mappedUpdate) - .is(matchedBy(isBsonObject().notContaining("$addToSet.nestedDocs.$each.[1]._class"))); + assertThat(mappedUpdate).doesNotContainKey("$addToSet.nestedDocs.$each.[0]._class") + .doesNotContainKey("$addToSet.nestedDocs.$each.[1]._class"); } @Test // DATAMONGO-1210 @@ -629,10 +619,8 @@ public class UpdateMapperUnitTests { Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(ListModelWrapper.class)); - assertThat((Bson) mappedUpdate) - .is(matchedBy(isBsonObject().containing("$addToSet.models.$each.[0]._class", ModelImpl.class.getName()))); - assertThat((Bson) mappedUpdate) - .is(matchedBy(isBsonObject().containing("$addToSet.models.$each.[1]._class", ModelImpl.class.getName()))); + assertThat(mappedUpdate).containsEntry("$addToSet.models.$each.[0]._class", ModelImpl.class.getName()); + assertThat(mappedUpdate).containsEntry("$addToSet.models.$each.[1]._class", ModelImpl.class.getName()); } @Test // DATAMONGO-1210 @@ -644,10 +632,8 @@ public class UpdateMapperUnitTests { Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(ParentClass.class)); - assertThat((Bson) mappedUpdate).is( - matchedBy(isBsonObject().containing("$addToSet.aliased.$each.[0]._class", ConcreteChildClass.class.getName()))); - assertThat((Bson) mappedUpdate).is( - matchedBy(isBsonObject().containing("$addToSet.aliased.$each.[1]._class", ConcreteChildClass.class.getName()))); + assertThat(mappedUpdate).containsEntry("$addToSet.aliased.$each.[0]._class", ConcreteChildClass.class.getName()); + assertThat(mappedUpdate).containsEntry("$addToSet.aliased.$each.[1]._class", ConcreteChildClass.class.getName()); } @Test // DATAMONGO-1210 @@ -660,11 +646,11 @@ public class UpdateMapperUnitTests { Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(DomainTypeWithListOfConcreteTypesHavingSingleInterfaceTypeAttribute.class)); - assertThat((Bson) mappedUpdate).is(matchedBy( - isBsonObject().notContaining("$addToSet.listHoldingConcretyTypeWithInterfaceTypeAttribute.$each.[0]._class"))); - assertThat((Bson) mappedUpdate).is(matchedBy(isBsonObject().containing( + assertThat(mappedUpdate) + .doesNotContainKey("$addToSet.listHoldingConcretyTypeWithInterfaceTypeAttribute.$each.[0]._class"); + assertThat(mappedUpdate).containsEntry( "$addToSet.listHoldingConcretyTypeWithInterfaceTypeAttribute.$each.[0].interfaceType._class", - ModelImpl.class.getName()))); + ModelImpl.class.getName()); } @Test // DATAMONGO-1210 @@ -677,10 +663,9 @@ public class UpdateMapperUnitTests { Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(DomainTypeWrappingConcreteyTypeHavingListOfInterfaceTypeAttributes.class)); - assertThat((Bson) mappedUpdate) - .is(matchedBy(isBsonObject().notContaining("$set.concreteTypeWithListAttributeOfInterfaceType._class"))); - assertThat((Bson) mappedUpdate).is(matchedBy(isBsonObject() - .containing("$set.concreteTypeWithListAttributeOfInterfaceType.models.[0]._class", ModelImpl.class.getName()))); + assertThat(mappedUpdate).doesNotContainKey("$set.concreteTypeWithListAttributeOfInterfaceType._class"); + assertThat(mappedUpdate).containsEntry("$set.concreteTypeWithListAttributeOfInterfaceType.models.[0]._class", + ModelImpl.class.getName()); } @Test // DATAMONGO-1809 @@ -707,8 +692,8 @@ public class UpdateMapperUnitTests { Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(EntityWithObject.class)); - Assert.assertThat(mappedUpdate, isBsonObject().containing("$set.value.name", "kaladin")); - Assert.assertThat(mappedUpdate, isBsonObject().containing("$set.value._class", NestedDocument.class.getName())); + assertThat(mappedUpdate).containsEntry("$set.value.name", "kaladin"); + assertThat(mappedUpdate).containsEntry("$set.value._class", NestedDocument.class.getName()); } @Test // DATAMONGO-1236 @@ -718,8 +703,8 @@ public class UpdateMapperUnitTests { Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(EntityWithObject.class)); - assertThat((Bson) mappedUpdate).is(matchedBy(isBsonObject().containing("$set.concreteValue.name", "shallan"))); - assertThat((Bson) mappedUpdate).is(matchedBy(isBsonObject().notContaining("$set.concreteValue._class"))); + assertThat(mappedUpdate).containsEntry("$set.concreteValue.name", "shallan"); + assertThat(mappedUpdate).doesNotContainKey("$set.concreteValue._class"); } @Test // DATAMONGO-1236 @@ -729,9 +714,8 @@ public class UpdateMapperUnitTests { Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(EntityWithAliasedObject.class)); - assertThat((Bson) mappedUpdate).is(matchedBy(isBsonObject().containing("$set.renamed-value.name", "adolin"))); - assertThat((Bson) mappedUpdate) - .is(matchedBy(isBsonObject().containing("$set.renamed-value._class", NestedDocument.class.getName()))); + assertThat(mappedUpdate).containsEntry("$set.renamed-value.name", "adolin"); + assertThat(mappedUpdate).containsEntry("$set.renamed-value._class", NestedDocument.class.getName()); } @Test // DATAMONGO-1236 @@ -743,9 +727,8 @@ public class UpdateMapperUnitTests { Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(EntityWithObjectMap.class)); - assertThat((Bson) mappedUpdate).is(matchedBy(isBsonObject().containing("$set.map.szeth.name", "son-son-vallano"))); - assertThat((Bson) mappedUpdate) - .is(matchedBy(isBsonObject().containing("$set.map.szeth._class", NestedDocument.class.getName()))); + assertThat(mappedUpdate).containsEntry("$set.map.szeth.name", "son-son-vallano"); + assertThat(mappedUpdate).containsEntry("$set.map.szeth._class", NestedDocument.class.getName()); } @Test // DATAMONGO-1236 @@ -757,8 +740,8 @@ public class UpdateMapperUnitTests { Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(EntityWithObjectMap.class)); - assertThat((Bson) mappedUpdate).is(matchedBy(isBsonObject().containing("$set.concreteMap.jasnah.name", "kholin"))); - assertThat((Bson) mappedUpdate).is(matchedBy(isBsonObject().notContaining("$set.concreteMap.jasnah._class"))); + assertThat(mappedUpdate).containsEntry("$set.concreteMap.jasnah.name", "kholin"); + assertThat(mappedUpdate).doesNotContainKey("$set.concreteMap.jasnah._class"); } @Test // DATAMONGO-1250 @@ -782,8 +765,7 @@ public class UpdateMapperUnitTests { Document result = mapper.getMappedObject(update.getUpdateObject(), mappingContext.getPersistentEntity(ClassWithEnum.class)); - assertThat((Bson) result) - .is(matchedBy(isBsonObject().containing("$set.allocation", ClassWithEnum.Allocation.AVAILABLE.code))); + assertThat(result).containsEntry("$set.allocation", ClassWithEnum.Allocation.AVAILABLE.code); } @Test // DATAMONGO-1251 @@ -795,8 +777,7 @@ public class UpdateMapperUnitTests { context.getPersistentEntity(ConcreteChildClass.class)); Document $set = DocumentTestUtils.getAsDocument(mappedUpdate, "$set"); - assertThat($set.containsKey("value")).isTrue(); - assertThat($set.get("value")).isNull(); + assertThat($set).containsKey("value").containsEntry("value", null); } @Test // DATAMONGO-1251 @@ -808,8 +789,7 @@ public class UpdateMapperUnitTests { context.getPersistentEntity(ClassWithJava8Date.class)); Document $set = DocumentTestUtils.getAsDocument(mappedUpdate, "$set"); - assertThat($set.containsKey("date")).isTrue(); - assertThat($set.get("value")).isNull(); + assertThat($set).containsKey("date").doesNotContainKey("value"); } @Test // DATAMONGO-1251 @@ -821,8 +801,7 @@ public class UpdateMapperUnitTests { context.getPersistentEntity(ListModel.class)); Document $set = DocumentTestUtils.getAsDocument(mappedUpdate, "$set"); - assertThat($set.containsKey("values")).isTrue(); - assertThat($set.get("value")).isNull(); + assertThat($set).containsKey("values").doesNotContainKey("value"); } @Test // DATAMONGO-1251 @@ -834,8 +813,8 @@ public class UpdateMapperUnitTests { context.getPersistentEntity(EntityWithObject.class)); Document $set = DocumentTestUtils.getAsDocument(mappedUpdate, "$set"); - assertThat($set.containsKey("concreteValue.name")).isTrue(); - assertThat($set.get("concreteValue.name")).isNull(); + assertThat($set).containsKey("concreteValue.name"); + assertThat($set).containsEntry("concreteValue.name", null); } @Test // DATAMONGO-1288 @@ -867,7 +846,7 @@ public class UpdateMapperUnitTests { Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(SimpleValueHolder.class)); - assertThat((Bson) mappedUpdate).is(matchedBy(isBsonObject().containing("$min", new Document("minfield", 10)))); + assertThat(mappedUpdate).containsEntry("$min", new Document("minfield", 10)); } @Test // DATAMONGO-1404 @@ -877,7 +856,7 @@ public class UpdateMapperUnitTests { Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(SimpleValueHolder.class)); - assertThat((Bson) mappedUpdate).is(matchedBy(isBsonObject().containing("$max", new Document("maxfield", 999)))); + assertThat(mappedUpdate).containsEntry("$max", new Document("maxfield", 999)); } @Test // DATAMONGO-1423 @@ -926,10 +905,10 @@ public class UpdateMapperUnitTests { Document mappedObject = mapper.getMappedObject(document, context.getPersistentEntity(SimpleValueHolder.class)); - assertThat(mappedObject.get("key")).isEqualTo("value"); - assertThat(mappedObject.get("a")).isEqualTo("b"); - assertThat(mappedObject.get("x")).isEqualTo("y"); - assertThat(mappedObject.size()).isEqualTo(3); + assertThat(mappedObject).containsEntry("key", "value"); + assertThat(mappedObject).containsEntry("a", "b"); + assertThat(mappedObject).containsEntry("x", "y"); + assertThat(mappedObject).hasSize(3); } @Test // DATAMONGO-1176 @@ -939,8 +918,8 @@ public class UpdateMapperUnitTests { Document mappedObject = mapper.getMappedObject(document, context.getPersistentEntity(SimpleValueHolder.class)); - assertThat(mappedObject.get("$push")).isEqualTo(new Document("x", "y")); - assertThat(mappedObject.get("$set")).isEqualTo(new Document("a", "b")); + assertThat(mappedObject).containsEntry("$push", new Document("x", "y")); + assertThat(mappedObject).containsEntry("$set", new Document("a", "b")); assertThat(mappedObject).hasSize(2); } @@ -968,9 +947,8 @@ public class UpdateMapperUnitTests { Document mappedUpdate = mapper.getMappedObject(new Update().set("concreteInnerList", list).getUpdateObject(), context.getPersistentEntity(Outer.class)); - assertThat((Bson) mappedUpdate) - .is(matchedBy(isBsonObject().containing("$set.concreteInnerList.[0].interfaceTypeList.[0]._class") - .notContaining("$set.concreteInnerList.[0]._class"))); + assertThat(mappedUpdate).containsKey("$set.concreteInnerList.[0].interfaceTypeList.[0]._class") + .doesNotContainKey("$set.concreteInnerList.[0]._class"); } @Test // DATAMONGO-1772 @@ -983,9 +961,8 @@ public class UpdateMapperUnitTests { Document mappedUpdate = mapper.getMappedObject(new Update().set("concreteInnerList", list).getUpdateObject(), context.getPersistentEntity(Outer.class)); - assertThat((Bson) mappedUpdate) - .is(matchedBy(isBsonObject().containing("$set.concreteInnerList.[0].abstractTypeList.[0]._class") - .notContaining("$set.concreteInnerList.[0]._class"))); + assertThat(mappedUpdate).containsKey("$set.concreteInnerList.[0].abstractTypeList.[0]._class") + .doesNotContainKey("$set.concreteInnerList.[0]._class"); } static class DomainTypeWrappingConcreteyTypeHavingListOfInterfaceTypeAttributes { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/AssertErrors.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/AssertErrors.java new file mode 100644 index 000000000..439c426b2 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/AssertErrors.java @@ -0,0 +1,80 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.test.util; + +import org.assertj.core.error.BasicErrorMessageFactory; +import org.assertj.core.error.ErrorMessageFactory; +import org.assertj.core.internal.StandardComparisonStrategy; + +/** + * Utility class providing factory methods for {@link ErrorMessageFactory}. + * + * @author Mark Paluch + */ +class AssertErrors { + + /** + * Creates a new {@link ShouldHaveProperty}. + * + * @param actual the actual value in the failed assertion. + * @param key the key used in the failed assertion to compare the actual property key to. + * @param value the value used in the failed assertion to compare the actual property value to. + * @return the created {@link ErrorMessageFactory}. + */ + public static ErrorMessageFactory shouldHaveProperty(Object actual, String key, Object value) { + return new ShouldHaveProperty(actual, key, value); + } + + /** + * Creates a new {@link ShouldNotHaveProperty}. + * + * @param actual the actual value in the failed assertion. + * @param key the key used in the failed assertion to compare the actual property key to. + * @param value the value used in the failed assertion to compare the actual property value to. + * @return the created {@link ErrorMessageFactory}. + */ + public static ErrorMessageFactory shouldNotHaveProperty(Object actual, String key, Object value) { + return new ShouldNotHaveProperty(actual, key, value); + } + + private static class ShouldHaveProperty extends BasicErrorMessageFactory { + + private ShouldHaveProperty(Object actual, String key, Object value) { + super("\n" + // + "Expecting:\n" + // + " <%s>\n" + // + "to have property with key:\n" + // + " <%s>\n" + // + "and value:\n" + // + " <%s>\n" + // + "%s", actual, key, value, StandardComparisonStrategy.instance()); + } + } + + private static class ShouldNotHaveProperty extends BasicErrorMessageFactory { + + private ShouldNotHaveProperty(Object actual, String key, Object value) { + super("\n" + // + "Expecting:\n" + // + " <%s>\n" + // + "not to have property with key:\n" + // + " <%s>\n" + // + "and value:\n" + // + " <%s>\n" + // + "but actually found such property %s", actual, key, value, StandardComparisonStrategy.instance()); + } + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/Assertions.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/Assertions.java new file mode 100644 index 000000000..99633fa5f --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/Assertions.java @@ -0,0 +1,41 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.test.util; + +import org.bson.Document; + +/** + * The entry point for all MongoDB assertions. This class extends {@link org.assertj.core.api.Assertions} for + * convenience to statically import a single class. + * + * @author Mark Paluch + */ +public abstract class Assertions extends org.assertj.core.api.Assertions { + + private Assertions() { + // no instances allowed. + } + + /** + * Create assertion for {@link Document}. + * + * @param actual the actual value. + * @return the created assertion object. + */ + public static DocumentAssert assertThat(Document document) { + return new DocumentAssert(document); + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/Conditions.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/Conditions.java deleted file mode 100644 index 6740d5d45..000000000 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/Conditions.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.mongodb.test.util; - -import lombok.RequiredArgsConstructor; - -import org.assertj.core.api.Condition; -import org.hamcrest.Matcher; -import org.hamcrest.StringDescription; - -/** - * All the things that cannot be done out of the box with {@link org.assertj.core.api.Assertions AssertJ}. - * - * @author Christoph Strobl - */ -public class Conditions { - - /** - * Get a {@link Condition} capable of asserting against a given {@link Matcher hamcest Matcher}. - * - * @param matcher must not be {@literal null}. - * @param - * @return - */ - public static Condition matchedBy(Matcher matcher) { - return new HamcrestCondition(matcher); - } - - @RequiredArgsConstructor - static class HamcrestCondition extends Condition { - - final Matcher matcher; - - public boolean matches(T value) { - - if (matcher.matches(value)) { - return true; - } - - setErrorMessage(value); - return false; - } - - private void setErrorMessage(T value) { - - StringDescription sd = new StringDescription(); - matcher.describeTo(sd); - as(sd.toString(), value); - } - } -} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/DocumentAssert.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/DocumentAssert.java new file mode 100644 index 000000000..3ee58353e --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/DocumentAssert.java @@ -0,0 +1,384 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.test.util; + +import static org.assertj.core.error.ElementsShouldBe.*; +import static org.assertj.core.error.ShouldContain.*; +import static org.assertj.core.error.ShouldContainKeys.*; +import static org.assertj.core.error.ShouldNotContain.*; +import static org.assertj.core.error.ShouldNotContainKeys.*; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.function.Consumer; + +import org.assertj.core.api.AbstractMapAssert; +import org.assertj.core.api.Condition; +import org.assertj.core.error.ShouldContainAnyOf; +import org.assertj.core.internal.Failures; +import org.bson.Document; +import org.bson.conversions.Bson; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * Assertions for Mongo's {@link Document}. Assertions based on keys/entries are translated to document paths allowing + * to assert nested elements. + * + *
+ * 
+ *  Document document = Document.parse("{ $set: { concreteInnerList: [ { foo: "bar", _class: … }] } }");
+ *
+ *  assertThat(mappedUpdate).containsKey("$set.concreteInnerList.[0].foo").doesNotContainKey("$set.concreteInnerList.[0].bar");
+ * 
+ * 
+ * + * @author Mark Paluch + */ +public class DocumentAssert extends AbstractMapAssert, String, Object> { + + private final Document actual; + + DocumentAssert(Document actual) { + + super(actual, DocumentAssert.class); + this.actual = actual; + } + + /* + * (non-Javadoc) + * @see org.assertj.core.api.AbstractMapAssert#containsEntry(java.lang.Object, java.lang.Object) + */ + @Override + public DocumentAssert containsEntry(String key, Object value) { + + Assert.hasText(key, "The key to look for must not be empty!"); + + Lookup lookup = lookup(key); + + if (!lookup.isPathFound() || !ObjectUtils.nullSafeEquals(value, lookup.getValue())) { + throw Failures.instance().failure(info, AssertErrors.shouldHaveProperty(actual, key, value)); + } + + return myself; + } + + /* + * (non-Javadoc) + * @see org.assertj.core.api.AbstractMapAssert#doesNotContainEntry(java.lang.Object, java.lang.Object) + */ + @Override + public DocumentAssert doesNotContainEntry(String key, Object value) { + + Assert.hasText(key, "The key to look for must not be empty!"); + + Lookup lookup = lookup(key); + + if (lookup.isPathFound() && ObjectUtils.nullSafeEquals(value, lookup.getValue())) { + throw Failures.instance().failure(info, AssertErrors.shouldNotHaveProperty(actual, key, value)); + } + + return myself; + } + + /* + * (non-Javadoc) + * @see org.assertj.core.api.AbstractMapAssert#containsKey(java.lang.Object) + */ + @Override + public DocumentAssert containsKey(String key) { + return containsKeys(key); + } + + /* + * (non-Javadoc) + * @see org.assertj.core.api.AbstractMapAssert#containsKeys(java.lang.Object[]) + */ + @Override + public final DocumentAssert containsKeys(String... keys) { + + Set notFound = new LinkedHashSet<>(); + + for (String key : keys) { + + if (!lookup(key).isPathFound()) { + notFound.add(key); + } + } + + if (!notFound.isEmpty()) { + throw Failures.instance().failure(info, shouldContainKeys(actual, notFound)); + } + + return myself; + } + + /* + * (non-Javadoc) + * @see org.assertj.core.api.AbstractMapAssert#doesNotContainKey(java.lang.Object) + */ + @Override + public DocumentAssert doesNotContainKey(String key) { + return doesNotContainKeys(key); + } + + /* + * (non-Javadoc) + * @see org.assertj.core.api.AbstractMapAssert#doesNotContainKeys(java.lang.Object[]) + */ + @Override + public final DocumentAssert doesNotContainKeys(String... keys) { + + Set found = new LinkedHashSet<>(); + for (String key : keys) { + + if (lookup(key).isPathFound()) { + found.add(key); + } + } + if (!found.isEmpty()) { + throw Failures.instance().failure(info, shouldNotContainKeys(actual, found)); + } + + return myself; + } + + // override methods to annotate them with @SafeVarargs, we unfortunately can't do that in AbstractMapAssert as it is + // used in soft assertions which need to be able to proxy method - @SafeVarargs requiring method to be final prevents + // using proxies. + + /* + * (non-Javadoc) + * @see org.assertj.core.api.AbstractMapAssert#contains(java.util.Map.Entry[]) + */ + @SafeVarargs + @Override + public final DocumentAssert contains(Map.Entry... entries) { + + // if both actual and values are empty, then assertion passes. + if (actual.isEmpty() && entries.length == 0) { + return myself; + } + Set> notFound = new LinkedHashSet<>(); + for (Map.Entry entry : entries) { + if (!containsEntry(entry)) { + notFound.add(entry); + } + } + if (!notFound.isEmpty()) { + throw Failures.instance().failure(info, shouldContain(actual, entries, notFound)); + } + + return myself; + } + + /* + * (non-Javadoc) + * @see org.assertj.core.api.AbstractMapAssert#containsAnyOf(java.util.Map.Entry[]) + */ + @SafeVarargs + @Override + public final DocumentAssert containsAnyOf(Map.Entry... entries) { + + for (Map.Entry entry : entries) { + if (containsEntry(entry)) { + return myself; + } + } + + throw Failures.instance().failure(info, ShouldContainAnyOf.shouldContainAnyOf(actual, entries)); + } + + /* + * (non-Javadoc) + * @see org.assertj.core.api.AbstractMapAssert#containsOnly(java.util.Map.Entry[]) + */ + @SafeVarargs + @Override + public final DocumentAssert containsOnly(Map.Entry... entries) { + throw new UnsupportedOperationException(); + } + + /* + * (non-Javadoc) + * @see org.assertj.core.api.AbstractMapAssert#doesNotContain(java.util.Map.Entry[]) + */ + @SafeVarargs + @Override + public final DocumentAssert doesNotContain(Map.Entry... entries) { + + Set> found = new LinkedHashSet<>(); + + for (Map.Entry entry : entries) { + if (containsEntry(entry)) { + found.add(entry); + } + } + if (!found.isEmpty()) { + throw Failures.instance().failure(info, shouldNotContain(actual, entries, found)); + } + + return myself; + } + + /* + * (non-Javadoc) + * @see org.assertj.core.api.AbstractMapAssert#containsExactly(java.util.Map.Entry[]) + */ + @SafeVarargs + @Override + public final DocumentAssert containsExactly(Map.Entry... entries) { + throw new UnsupportedOperationException(); + } + + private boolean containsEntry(Entry entry) { + + Lookup lookup = lookup(entry.getKey()); + + return lookup.isPathFound() && ObjectUtils.nullSafeEquals(entry.getValue(), lookup.getValue()); + } + + private Lookup lookup(String path) { + return lookup(actual, path); + } + + @SuppressWarnings("unchecked") + private static Lookup lookup(Bson source, String path) { + + String[] fragments = path.split("(? it = Arrays.asList(fragments).iterator(); + + Object current = source; + while (it.hasNext()) { + + String key = it.next().replace("\\.", "."); + + if (!(current instanceof Bson) && !key.startsWith("[")) { + return Lookup.found(null); + } + + if (key.startsWith("[")) { + + String indexNumber = key.substring(1, key.indexOf("]")); + + if (current instanceof List) { + current = ((List) current).get(Integer.valueOf(indexNumber)); + } + + if (!it.hasNext()) { + return Lookup.found((T) current); + } + } else { + + if (current instanceof Document) { + + Document document = (Document) current; + + if (!it.hasNext() && !document.containsKey(key)) { + return Lookup.notFound(); + } + + current = document.get(key); + } + + if (!it.hasNext()) { + return Lookup.found((T) current); + } + } + } + + return Lookup.notFound(); + } + + /* + * (non-Javadoc) + * @see org.assertj.core.api.AbstractMapAssert#hasEntrySatisfying(java.lang.Object, org.assertj.core.api.Condition) + */ + @Override + public DocumentAssert hasEntrySatisfying(String key, Condition valueCondition) { + + Lookup value = lookup(key); + + if (!value.isPathFound() || !valueCondition.matches(value.getValue())) { + throw Failures.instance().failure(info, elementsShouldBe(actual, value, valueCondition)); + } + + return myself; + } + + /* + * (non-Javadoc) + * @see org.assertj.core.api.AbstractMapAssert#hasEntrySatisfying(java.lang.Object, java.util.function.Consumer) + */ + @Override + public DocumentAssert hasEntrySatisfying(String key, Consumer valueRequirements) { + + containsKey(key); + + valueRequirements.accept(lookup(key).getValue()); + + return myself; + } + + @RequiredArgsConstructor(access = AccessLevel.PRIVATE) + @Getter + static class Lookup { + + private final T value; + private final boolean pathFound; + + /** + * Factory method to construct a lookup with a hit. + * + * @param value the actual value. + * @return the lookup object. + */ + static Lookup found(T value) { + return new Lookup<>(value, true); + } + + /** + * Factory method to construct a lookup that yielded no match. + * + * @return the lookup object. + */ + static Lookup notFound() { + return new Lookup<>(null, false); + } + } +}