Skip to content

Commit 6b87492

Browse files
committed
Polishing.
Refactor fixture creation for easier readability. Tweak documentation wording. See #4571 Original pull request: #4574
1 parent e5b9f7d commit 6b87492

File tree

2 files changed

+153
-57
lines changed

2 files changed

+153
-57
lines changed

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java

+146-53
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.time.LocalDateTime;
2828
import java.time.temporal.ChronoUnit;
2929
import java.util.*;
30+
import java.util.function.Consumer;
3031
import java.util.function.Function;
3132
import java.util.stream.Stream;
3233

@@ -677,15 +678,144 @@ void readsListOfMapsCorrectly() {
677678
assertThat(wrapper.listOfMaps.get(0).get("Foo")).isEqualTo(Locale.ENGLISH);
678679
}
679680

680-
@ParameterizedTest // GH-4571
681+
@ParameterizedTest(name = "{4}") // GH-4571
681682
@MethodSource("listMapSetReadingSource")
682683
<T> void initializesListMapSetPropertiesIfRequiredOnRead(org.bson.Document source, Class<T> type,
683-
Function<T, Object> valueFunction, Object expectedValue) {
684+
Function<T, Object> valueFunction, Object expectedValue, String displayName) {
684685

685686
T target = converter.read(type, source);
686687
assertThat(target).extracting(valueFunction).isEqualTo(expectedValue);
687688
}
688689

690+
private static Stream<Arguments> listMapSetReadingSource() {
691+
692+
Stream<Arguments> initialList = fixtureFor("contacts", CollectionWrapper.class, CollectionWrapper::getContacts,
693+
builder -> {
694+
695+
builder.onValue(Collections.emptyList()).expect(Collections.emptyList());
696+
builder.onNull().expect(null);
697+
builder.onEmpty().expect(null);
698+
});
699+
700+
Stream<Arguments> initializedList = fixtureFor("autoInitList", CollectionWrapper.class,
701+
CollectionWrapper::getAutoInitList, builder -> {
702+
703+
builder.onValue(Collections.emptyList()).expect(Collections.emptyList());
704+
builder.onNull().expect(null);
705+
builder.onEmpty().expect(Collections.singletonList("spring"));
706+
});
707+
708+
Stream<Arguments> initialSet = fixtureFor("contactsSet", CollectionWrapper.class, CollectionWrapper::getContactsSet,
709+
builder -> {
710+
711+
builder.onValue(Collections.emptyList()).expect(Collections.emptySet());
712+
builder.onNull().expect(null);
713+
builder.onEmpty().expect(null);
714+
});
715+
716+
Stream<Arguments> initialMap = fixtureFor("map", ClassWithMapProperty.class, ClassWithMapProperty::getMap,
717+
builder -> {
718+
719+
builder.onValue(new org.bson.Document()).expect(Collections.emptyMap());
720+
builder.onNull().expect(null);
721+
builder.onEmpty().expect(null);
722+
});
723+
724+
Stream<Arguments> initializedMap = fixtureFor("autoInitMap", ClassWithMapProperty.class,
725+
ClassWithMapProperty::getAutoInitMap, builder -> {
726+
727+
builder.onValue(new org.bson.Document()).expect(Collections.emptyMap());
728+
builder.onNull().expect(null);
729+
builder.onEmpty().expect(Collections.singletonMap("spring", "data"));
730+
});
731+
732+
return Stream.of(initialList, initializedList, initialSet, initialMap, initializedMap).flatMap(Function.identity());
733+
}
734+
735+
static <T> Stream<Arguments> fixtureFor(String field, Class<T> type, Function<T, Object> valueFunction,
736+
Consumer<FixtureBuilder> builderConsumer) {
737+
738+
FixtureBuilder builder = new FixtureBuilder(field, type, valueFunction);
739+
740+
builderConsumer.accept(builder);
741+
742+
return builder.fixtures.stream();
743+
}
744+
745+
/**
746+
* Builder for fixtures.
747+
*/
748+
static class FixtureBuilder {
749+
750+
private final String field;
751+
private final Class<?> typeUnderTest;
752+
private final Function<?, Object> valueMappingFunction;
753+
final List<Arguments> fixtures = new ArrayList<>();
754+
755+
FixtureBuilder(String field, Class<?> typeUnderTest, Function<?, Object> valueMappingFunction) {
756+
this.field = field;
757+
this.typeUnderTest = typeUnderTest;
758+
this.valueMappingFunction = valueMappingFunction;
759+
}
760+
761+
/**
762+
* If the document value is {@code null}.
763+
*/
764+
FixtureStep onNull() {
765+
return new FixtureStep(false, null);
766+
}
767+
768+
/**
769+
* If the document value is {@code value}.
770+
*/
771+
FixtureStep onValue(@Nullable Object value) {
772+
return new FixtureStep(false, value);
773+
}
774+
775+
/**
776+
* If the document does not contain the field.
777+
*/
778+
FixtureStep onEmpty() {
779+
return new FixtureStep(true, null);
780+
}
781+
782+
class FixtureStep {
783+
784+
private final boolean empty;
785+
private final @Nullable Object documentValue;
786+
787+
public FixtureStep(boolean empty, @Nullable Object documentValue) {
788+
this.empty = empty;
789+
this.documentValue = documentValue;
790+
}
791+
792+
/**
793+
* Then expect {@code expectedValue}.
794+
*
795+
* @param expectedValue
796+
*/
797+
void expect(@Nullable Object expectedValue) {
798+
799+
Arguments fixture;
800+
if (empty) {
801+
fixture = Arguments.of(new org.bson.Document(), typeUnderTest, valueMappingFunction, expectedValue,
802+
"Empty document expecting '%s' at type %s".formatted(expectedValue, typeUnderTest.getSimpleName()));
803+
} else {
804+
805+
String valueDescription = (documentValue == null ? "null"
806+
: (documentValue + " (" + documentValue.getClass().getSimpleName()) + ")");
807+
808+
fixture = Arguments.of(new org.bson.Document(field, documentValue), typeUnderTest, valueMappingFunction,
809+
expectedValue, "Field '%s' with value %s expecting '%s' at type %s".formatted(field, valueDescription,
810+
expectedValue, typeUnderTest.getSimpleName()));
811+
}
812+
813+
fixtures.add(fixture);
814+
}
815+
}
816+
817+
}
818+
689819
@Test // DATAMONGO-259
690820
void writesPlainMapOfCollectionsCorrectly() {
691821

@@ -2931,7 +3061,8 @@ void shouldNotSplitKeyNamesWithDotOnReadOfNestedIfFieldTypeIsKey() {
29313061

29323062
org.bson.Document source = new org.bson.Document("nested", new org.bson.Document("field.name.with.dots", "A"));
29333063

2934-
WrapperForTypeWithPropertyHavingDotsInFieldName target = converter.read(WrapperForTypeWithPropertyHavingDotsInFieldName.class, source);
3064+
WrapperForTypeWithPropertyHavingDotsInFieldName target = converter
3065+
.read(WrapperForTypeWithPropertyHavingDotsInFieldName.class, source);
29353066
assertThat(target.nested).isNotNull();
29363067
assertThat(target.nested.value).isEqualTo("A");
29373068
}
@@ -2964,14 +3095,16 @@ void readShouldAllowDotsInMapKeyNameIfConfigured() {
29643095
person.firstname = "bart";
29653096
person.lastname = "simpson";
29663097

2967-
org.bson.Document source = new org.bson.Document("mapOfPersons", new org.bson.Document("map.key.with.dots", write(person)));
3098+
org.bson.Document source = new org.bson.Document("mapOfPersons",
3099+
new org.bson.Document("map.key.with.dots", write(person)));
29683100

29693101
ClassWithMapProperty target = converter.read(ClassWithMapProperty.class, source);
29703102

29713103
assertThat(target.mapOfPersons).containsEntry("map.key.with.dots", person);
29723104
}
29733105

2974-
@ValueSource(classes = { ComplexIdAndNoAnnotation.class, ComplexIdAndIdAnnotation.class, ComplexIdAndMongoIdAnnotation.class, ComplexIdAndFieldAnnotation.class })
3106+
@ValueSource(classes = { ComplexIdAndNoAnnotation.class, ComplexIdAndIdAnnotation.class,
3107+
ComplexIdAndMongoIdAnnotation.class, ComplexIdAndFieldAnnotation.class })
29753108
@ParameterizedTest // GH-4524
29763109
void projectShouldReadComplexIdType(Class<?> projectionTargetType) {
29773110

@@ -2999,49 +3132,6 @@ org.bson.Document write(Object source) {
29993132
return target;
30003133
}
30013134

3002-
private static Stream<Arguments> listMapSetReadingSource() {
3003-
3004-
Function<CollectionWrapper, Object> contacts = CollectionWrapper::getContacts;
3005-
Function<CollectionWrapper, Object> contactsSet = CollectionWrapper::getContactsSet;
3006-
Function<CollectionWrapper, Object> autoInitList = CollectionWrapper::getAutoInitList;
3007-
Function<ClassWithMapProperty, Object> map = ClassWithMapProperty::getMap;
3008-
Function<ClassWithMapProperty, Object> autoInitMap = ClassWithMapProperty::getAutoInitMap;
3009-
3010-
return Stream.of( //
3011-
3012-
// List
3013-
Arguments.of(new org.bson.Document("contacts", Collections.emptyList()), CollectionWrapper.class, contacts,
3014-
Collections.emptyList()),
3015-
Arguments.of(new org.bson.Document("contacts", null), CollectionWrapper.class, contacts, null),
3016-
Arguments.of(new org.bson.Document(), CollectionWrapper.class, contacts, null),
3017-
3018-
// ctor initialized List
3019-
Arguments.of(new org.bson.Document("autoInitList", Collections.emptyList()), CollectionWrapper.class,
3020-
autoInitList, Collections.emptyList()),
3021-
Arguments.of(new org.bson.Document("autoInitList", null), CollectionWrapper.class, autoInitList, null),
3022-
Arguments.of(new org.bson.Document(), CollectionWrapper.class, autoInitList,
3023-
Collections.singletonList("spring")),
3024-
3025-
// Set
3026-
Arguments.of(new org.bson.Document("contactsSet", Collections.emptyList()), CollectionWrapper.class,
3027-
contactsSet, Collections.emptySet()),
3028-
Arguments.of(new org.bson.Document("contactsSet", null), CollectionWrapper.class, contactsSet, null),
3029-
Arguments.of(new org.bson.Document(), CollectionWrapper.class, contactsSet, null),
3030-
3031-
// Map
3032-
Arguments.of(new org.bson.Document("map", new org.bson.Document()), ClassWithMapProperty.class, map,
3033-
Collections.emptyMap()),
3034-
Arguments.of(new org.bson.Document("map", null), ClassWithMapProperty.class, map, null),
3035-
Arguments.of(new org.bson.Document(), ClassWithMapProperty.class, map, null),
3036-
3037-
// ctor initialized Map
3038-
Arguments.of(new org.bson.Document("autoInitMap", new org.bson.Document()), ClassWithMapProperty.class,
3039-
autoInitMap, Collections.emptyMap()),
3040-
Arguments.of(new org.bson.Document("autoInitMap", null), ClassWithMapProperty.class, autoInitMap, null),
3041-
Arguments.of(new org.bson.Document(), ClassWithMapProperty.class, autoInitMap,
3042-
Collections.singletonMap("spring", "data")));
3043-
}
3044-
30453135
static class GenericType<T> {
30463136
T content;
30473137
}
@@ -3135,7 +3225,9 @@ public boolean equals(Object o) {
31353225
return false;
31363226
}
31373227
Person person = (Person) o;
3138-
return Objects.equals(id, person.id) && Objects.equals(birthDate, person.birthDate) && Objects.equals(firstname, person.firstname) && Objects.equals(lastname, person.lastname) && Objects.equals(addresses, person.addresses);
3228+
return Objects.equals(id, person.id) && Objects.equals(birthDate, person.birthDate)
3229+
&& Objects.equals(firstname, person.firstname) && Objects.equals(lastname, person.lastname)
3230+
&& Objects.equals(addresses, person.addresses);
31393231
}
31403232

31413233
@Override
@@ -3922,10 +4014,12 @@ static class WithFieldWrite {
39224014
@org.springframework.data.mongodb.core.mapping.Field(
39234015
write = org.springframework.data.mongodb.core.mapping.Field.Write.ALWAYS) Integer writeAlways;
39244016

3925-
@org.springframework.data.mongodb.core.mapping.DBRef @org.springframework.data.mongodb.core.mapping.Field(
4017+
@org.springframework.data.mongodb.core.mapping.DBRef
4018+
@org.springframework.data.mongodb.core.mapping.Field(
39264019
write = org.springframework.data.mongodb.core.mapping.Field.Write.NON_NULL) Person writeNonNullPerson;
39274020

3928-
@org.springframework.data.mongodb.core.mapping.DBRef @org.springframework.data.mongodb.core.mapping.Field(
4021+
@org.springframework.data.mongodb.core.mapping.DBRef
4022+
@org.springframework.data.mongodb.core.mapping.Field(
39294023
write = org.springframework.data.mongodb.core.mapping.Field.Write.ALWAYS) Person writeAlwaysPerson;
39304024

39314025
}
@@ -4157,8 +4251,7 @@ static class WrapperForTypeWithPropertyHavingDotsInFieldName {
41574251

41584252
static class WithPropertyHavingDotsInFieldName {
41594253

4160-
@Field(name = "field.name.with.dots", nameType = Type.KEY)
4161-
String value;
4254+
@Field(name = "field.name.with.dots", nameType = Type.KEY) String value;
41624255
}
41634256

41644257
static class ComplexIdAndFieldAnnotation {

src/main/antora/modules/ROOT/pages/mongodb/mapping/mapping.adoc

+7-4
Original file line numberDiff line numberDiff line change
@@ -277,12 +277,15 @@ calling `get()` before the actual conversion
277277
.Collection Handling
278278
[NOTE]
279279
====
280-
Collection handing depends on the actual values retrieved from the MongoDB.
280+
Collection handling depends on the actual values returned by MongoDB.
281281
282-
* If a document does **not** contain the field mapped to a collection, the mapping will not touch the property.
282+
* If a document does **not** contain a field mapped to a collection, the mapping will not update the property.
283283
Which means the value will remain `null`, a java default or any value set during object creation.
284-
* If the document contains the field to be mapped, but the field holds a `null` value (like: `{ 'list' : null }`), the property value is set to `null` overriding any default value set during object creation.
285-
* If the document contains the field to be mapped to a collection which is **not** `null` (like: `{ 'list' : [ ... ] }`), the collection is populated with the mapped values overriding any default value set during object creation.
284+
* If a document contains a field to be mapped, but the field holds a `null` value (like: `{ 'list' : null }`), the property value is set to `null`.
285+
* If a document contains a field to be mapped to a collection which is **not** `null` (like: `{ 'list' : [ ... ] }`), the collection is populated with the mapped values.
286+
287+
Generally, if you use constructor creation, then you can get hold of the value to be set.
288+
Property population can make use of default initialization values if a property value is not being provided by a query response.
286289
====
287290

288291
[[mapping-configuration]]

0 commit comments

Comments
 (0)