DATAMONGO-1809 - Introduce AssertJ assertions for Document.

Original pull request: #508.
This commit is contained in:
Mark Paluch
2017-10-24 12:02:00 +02:00
parent 296a8102d0
commit 436b6994e1
5 changed files with 570 additions and 152 deletions

View File

@@ -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 {

View File

@@ -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());
}
}
}

View File

@@ -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);
}
}

View File

@@ -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 <T>
* @return
*/
public static <T> Condition<T> matchedBy(Matcher<? super T> matcher) {
return new HamcrestCondition(matcher);
}
@RequiredArgsConstructor
static class HamcrestCondition<T> extends Condition<T> {
final Matcher<? super T> 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);
}
}
}

View File

@@ -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.
*
* <pre>
* <code>
* Document document = Document.parse("{ $set: { concreteInnerList: [ { foo: "bar", _class: … }] } }");
*
* assertThat(mappedUpdate).containsKey("$set.concreteInnerList.[0].foo").doesNotContainKey("$set.concreteInnerList.[0].bar");
* </code>
* </pre>
*
* @author Mark Paluch
*/
public class DocumentAssert extends AbstractMapAssert<DocumentAssert, Map<String, Object>, 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<String> 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<String> 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<? extends String, ? extends Object>... entries) {
// if both actual and values are empty, then assertion passes.
if (actual.isEmpty() && entries.length == 0) {
return myself;
}
Set<Map.Entry<? extends String, ? extends Object>> notFound = new LinkedHashSet<>();
for (Map.Entry<? extends String, ? extends Object> 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<? extends String, ? extends Object>... entries) {
for (Map.Entry<? extends String, ? extends Object> 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<? extends String, ? extends Object>... 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<? extends String, ? extends Object>... entries) {
Set<Map.Entry<? extends String, ? extends Object>> found = new LinkedHashSet<>();
for (Map.Entry<? extends String, ? extends Object> 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<? extends String, ? extends Object>... entries) {
throw new UnsupportedOperationException();
}
private boolean containsEntry(Entry<? extends String, ?> entry) {
Lookup<?> lookup = lookup(entry.getKey());
return lookup.isPathFound() && ObjectUtils.nullSafeEquals(entry.getValue(), lookup.getValue());
}
private <T> Lookup<T> lookup(String path) {
return lookup(actual, path);
}
@SuppressWarnings("unchecked")
private static <T> Lookup<T> lookup(Bson source, String path) {
String[] fragments = path.split("(?<!\\\\)\\.");
if (fragments.length == 1) {
Document document = (Document) source;
String pathToUse = path.replace("\\.", ".");
if (document.containsKey(pathToUse)) {
return Lookup.found((T) document.get(pathToUse));
}
return Lookup.notFound();
}
Iterator<String> 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<? super Object> valueCondition) {
Lookup<Object> 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<? super Object> valueRequirements) {
containsKey(key);
valueRequirements.accept(lookup(key).getValue());
return myself;
}
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
@Getter
static class Lookup<T> {
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 <T> Lookup<T> found(T value) {
return new Lookup<>(value, true);
}
/**
* Factory method to construct a lookup that yielded no match.
*
* @return the lookup object.
*/
static <T> Lookup<T> notFound() {
return new Lookup<>(null, false);
}
}
}