From 1efdbfeeb7ce36244b3837679cd9a6fc43340019 Mon Sep 17 00:00:00 2001 From: Shawn Kovalchick Date: Mon, 6 Oct 2025 19:06:38 -0400 Subject: [PATCH 1/8] Add failing tests highlighting the bug Signed-off-by: Shawn Kovalchick --- .../DefaultDbRefResolverUnitTests.java | 82 ++++++++++++++++++- .../convert/MappingMongoConverterTests.java | 63 ++++++++++++++ .../ReferenceLookupDelegateUnitTests.java | 34 ++++++++ 3 files changed, 178 insertions(+), 1 deletion(-) diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolverUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolverUnitTests.java index 75c7cc4366..ae54c8afa4 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolverUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolverUnitTests.java @@ -21,10 +21,12 @@ import java.util.Arrays; import java.util.Collections; +import java.util.Map; import org.bson.Document; import org.bson.types.ObjectId; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; @@ -35,6 +37,8 @@ import org.mockito.quality.Strictness; import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.mapping.model.SpELContext; import org.springframework.data.mongodb.MongoDatabaseFactory; import org.springframework.data.mongodb.core.DocumentTestUtils; import org.springframework.data.mongodb.core.MongoExceptionTranslator; @@ -43,6 +47,9 @@ import com.mongodb.client.FindIterable; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoDatabase; +import org.springframework.data.mongodb.core.mapping.DocumentReference; +import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; +import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; /** * Unit tests for {@link DefaultDbRefResolver}. @@ -58,6 +65,8 @@ class DefaultDbRefResolverUnitTests { @Mock MongoDatabase dbMock; @Mock MongoCollection collectionMock; @Mock FindIterable cursorMock; + @Mock MappingContext, MongoPersistentProperty> mappingContext; + @Mock SpELContext spELContext; private DefaultDbRefResolver resolver; @BeforeEach @@ -91,7 +100,7 @@ void bulkFetchShouldLoadDbRefsCorrectly() { } @Test // DATAMONGO-1194 - void bulkFetchShouldThrowExceptionWhenUsingDifferntCollectionsWithinSetOfReferences() { + void bulkFetchShouldThrowExceptionWhenUsingDifferentCollectionsWithinSetOfReferences() { DBRef ref1 = new DBRef("collection-1", new ObjectId()); DBRef ref2 = new DBRef("collection-2", new ObjectId()); @@ -134,4 +143,75 @@ void bulkFetchContainsDuplicates() { assertThat(resolver.bulkFetch(Arrays.asList(ref1, ref2))).containsExactly(document, document); } + + @Test // GH-5065 + @DisplayName("GH-5065: Empty Map with @DocumentReference annotation should deserialize to an empty map.") + void resolveEmptyMapIsNotNull() { + DocumentReference documentReference = mock(DocumentReference.class); + when(documentReference.lookup()).thenReturn("{ '_id' : ?#{#target} }"); + when(documentReference.sort()).thenReturn(""); + when(documentReference.lazy()).thenReturn(false); + MongoPersistentProperty property = mock(MongoPersistentProperty.class); + when(property.isCollectionLike()).thenReturn(false); + when(property.isMap()).thenReturn(true); + when(property.isDocumentReference()).thenReturn(true); + when(property.getDocumentReference()).thenReturn(documentReference); + DocumentReferenceSource source = mock(DocumentReferenceSource.class); + when(source.getTargetSource()).thenReturn(Document.parse("{}")); + ReferenceLookupDelegate lookupDelegate = new ReferenceLookupDelegate(mappingContext, spELContext); + + ReferenceResolver.MongoEntityReader entityReader = mock(ReferenceResolver.MongoEntityReader.class); + + Object target = resolver.resolveReference(property, source, lookupDelegate, entityReader); + + verify(property, times(3)).isMap(); + verify(property, times(2)).isDocumentReference(); + verify(property, times(2)).getDocumentReference(); + verify(property, times(3)).isCollectionLike(); + verify(documentReference, times(1)).lookup(); + verify(documentReference, times(1)).sort(); + verify(documentReference, times(1)).lazy(); + verify(source, times(3)).getTargetSource(); + verifyNoMoreInteractions(documentReference, property, source); // Make sure we only call the properties we mocked. + assertThat(target) + .isNotNull() + .isInstanceOf(Map.class); + } + + @Test // GH-5065 + @DisplayName("GH-5065: Lazy loaded empty Map with @DocumentReference annotation should deserialize to an empty map with a non-null values property.") + void resolveLazyLoadedEmptyMapIsNotNull() { + DocumentReference documentReference = mock(DocumentReference.class); + when(documentReference.lookup()).thenReturn("{ '_id' : ?#{#target} }"); + when(documentReference.sort()).thenReturn(""); + when(documentReference.lazy()).thenReturn(true); + MongoPersistentProperty property = mock(MongoPersistentProperty.class); + when(property.isCollectionLike()).thenReturn(false); + when(property.isMap()).thenReturn(true); + when(property.isDocumentReference()).thenReturn(true); + when(property.getDocumentReference()).thenReturn(documentReference); + //noinspection rawtypes,unchecked + when(property.getType()).thenReturn((Class) Map.class); + DocumentReferenceSource source = mock(DocumentReferenceSource.class); + when(source.getTargetSource()).thenReturn(Document.parse("{}")); + ReferenceLookupDelegate lookupDelegate = new ReferenceLookupDelegate(mappingContext, spELContext); + + ReferenceResolver.MongoEntityReader entityReader = mock(ReferenceResolver.MongoEntityReader.class); + + Object target = resolver.resolveReference(property, source, lookupDelegate, entityReader); + + verify(property, times(1)).isMap(); + verify(property, times(1)).isDocumentReference(); + verify(property, times(1)).getDocumentReference(); + verify(property, times(1)).isCollectionLike(); + verify(property, times(1)).getType(); + verify(documentReference, times(1)).lazy(); + verify(source, times(1)).getTargetSource(); + verifyNoMoreInteractions(documentReference, property, source); // Make sure we only call the properties we mocked. + + assertThat(target) + .isNotNull() + .isInstanceOf(Map.class) + .asInstanceOf(MAP).values().isNotNull(); + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterTests.java index be8dbced9e..ee1b031a5d 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterTests.java @@ -34,6 +34,8 @@ import org.bson.Document; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.data.annotation.Id; @@ -41,6 +43,7 @@ import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory; import org.springframework.data.mongodb.core.convert.MongoCustomConversions.MongoConverterConfigurationAdapter; import org.springframework.data.mongodb.core.mapping.DBRef; +import org.springframework.data.mongodb.core.mapping.DocumentReference; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.test.util.Client; @@ -478,4 +481,64 @@ public String toString() { + ", localTime=" + this.getLocalTime() + ", localDateTime=" + this.getLocalDateTime() + ")"; } } + + @Nested // GH-5065 + @DisplayName("GH-5065: Empty Map Tests") + class EmptyMapTests { + + @Test // GH-5065 + @DisplayName("Passing test to indicate that the problem is not a more generic issue with maps in general.") + void readsEmptyMapCorrectly() { + org.bson.Document document = org.bson.Document.parse("{\"map\":{}}"); + EmptyMapDocument target = converter.read(EmptyMapDocument.class, document); + assertThat(target.map).isNotNull().isEmpty(); + } + + @Test // GH-5065 + @DisplayName("Converter should read an empty object as an empty map when using @DocumentReference.") + void readsEmptyMapWithDocumentReferenceCorrectly() { + org.bson.Document document = org.bson.Document.parse("{\"map\":{}}"); + DocumentReferenceEmptyMapDocument target = converter.read(DocumentReferenceEmptyMapDocument.class, document); + assertThat(target.map).isNotNull().isEmpty(); + } + + @Test // GH-5065 + @DisplayName("Converter should read an empty object as an empty map with a valis values property when using @DocumentReference(lazy = true).") + void readsEmptyMapWithLazyLoadedDocumentReferenceCorrectly() { + org.bson.Document document = org.bson.Document.parse("{\"map\":{}}"); + LazyDocumentReferenceEmptyMapDocument target = converter.read(LazyDocumentReferenceEmptyMapDocument.class, document); + assertThat(target.map).isNotNull(); + assertThat(target.map.values()).isNotNull(); + } + + static class EmptyMapDocument { + + Map map; + + public EmptyMapDocument(Map map) { + this.map = map; + } + } + + static class DocumentReferenceEmptyMapDocument { + + @DocumentReference + Map map; + + public DocumentReferenceEmptyMapDocument(Map map) { + this.map = map; + } + } + + static class LazyDocumentReferenceEmptyMapDocument { + + @DocumentReference(lazy = true) + Map map; + + public LazyDocumentReferenceEmptyMapDocument(Map map) { + this.map = map; + } + } + } + } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegateUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegateUnitTests.java index 384cffaad4..c7d9f3494a 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegateUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegateUnitTests.java @@ -19,8 +19,11 @@ import static org.mockito.Mockito.*; import java.util.Collections; +import java.util.Map; +import org.bson.Document; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -94,4 +97,35 @@ void shouldResolveEmptyMapOnEmptyTargetCollection() { lookupDelegate.readReference(property, Collections.emptyMap(), lookupFunction, entityReader); verify(lookupFunction, never()).apply(any(), any()); } + + @Test // GH-5065 + @DisplayName("GH-5065: Empty Map with @DocumentReference annotation should deserialize to an empty map.") + void shouldResolveEmptyMapOnEmptyDocumentReferenceMapProperty() { + DocumentReference documentReference = mock(DocumentReference.class); + when(documentReference.lookup()).thenReturn("{ '_id' : ?#{#target} }"); + when(documentReference.sort()).thenReturn(""); + MongoPersistentProperty property = mock(MongoPersistentProperty.class); + when(property.isMap()).thenReturn(true); + when(property.isDocumentReference()).thenReturn(true); + when(property.getDocumentReference()).thenReturn(documentReference); + when(property.isCollectionLike()).thenReturn(false); + DocumentReferenceSource source = mock(DocumentReferenceSource.class); + when(source.getTargetSource()).thenReturn(Document.parse("{}")); + ReferenceLookupDelegate.LookupFunction lookupFunction = mock(ReferenceLookupDelegate.LookupFunction.class); + + Object target = lookupDelegate.readReference(property, source, lookupFunction, entityReader); + + verify(lookupFunction, never()).apply(any(), any()); // Since we mocked it, make sure it is never called. + verify(property, times(2)).isMap(); + verify(property, times(1)).isDocumentReference(); + verify(property, times(1)).getDocumentReference(); + verify(property, times(2)).isCollectionLike(); + verify(documentReference, times(1)).lookup(); + verify(documentReference, times(1)).sort(); + verifyNoMoreInteractions(documentReference, property, source); // Make sure we only call the properties we mocked. + assertThat(target) + .isNotNull() + .isInstanceOf(Map.class); + } + } From 18a1d5560e705536ed0f6631453cde6168f5c53e Mon Sep 17 00:00:00 2001 From: Shawn Kovalchick Date: Mon, 6 Oct 2025 19:09:27 -0400 Subject: [PATCH 2/8] Return empty map instead of null when the property is a map Signed-off-by: Shawn Kovalchick --- .../data/mongodb/core/convert/ReferenceLookupDelegate.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegate.java index a0e5a6f2bb..088b27e70b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegate.java @@ -116,7 +116,7 @@ public ReferenceLookupDelegate( } if (!result.iterator().hasNext()) { - return null; + return property.isMap() ? Collections.emptyMap() : null; } Object resultValue = result.iterator().next(); From 3338cea0b45742bf452e3794bac3dc26abd83102 Mon Sep 17 00:00:00 2001 From: Shawn Kovalchick Date: Wed, 15 Oct 2025 23:44:55 -0400 Subject: [PATCH 3/8] Adding tests for explicitly assigned nulls and repeating conversion to prevent lazy proxy handling between tests Signed-off-by: Shawn Kovalchick --- .../convert/MappingMongoConverterTests.java | 84 +++++++++++++++++-- 1 file changed, 75 insertions(+), 9 deletions(-) diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterTests.java index ee1b031a5d..a84aca5d3b 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterTests.java @@ -487,28 +487,68 @@ public String toString() { class EmptyMapTests { @Test // GH-5065 - @DisplayName("Passing test to indicate that the problem is not a more generic issue with maps in general.") + @DisplayName("Test control: Passing test to indicate that the problem is not a more generic issue with maps in general.") void readsEmptyMapCorrectly() { org.bson.Document document = org.bson.Document.parse("{\"map\":{}}"); - EmptyMapDocument target = converter.read(EmptyMapDocument.class, document); - assertThat(target.map).isNotNull().isEmpty(); + //EmptyMapDocument target = converter.read(EmptyMapDocument.class, document); + assertThat(converter.read(EmptyMapDocument.class, document).map).isNotNull().isEmpty(); + assertThat(converter.read(EmptyMapDocument.class, document).getMap()).isNotNull().isEmpty(); + } + + @Test // GH-5065 + @DisplayName("Test control: Converter should read an explicitly assigned null as a null map.") + void readsExplicitlyNullMapCorrectly() { + org.bson.Document document = org.bson.Document.parse("{\"map\":null}"); + //EmptyMapDocument target = converter.read(EmptyMapDocument.class, document); + assertThat(converter.read(EmptyMapDocument.class, document).map).isNull(); + assertThat(converter.read(EmptyMapDocument.class, document).getMap()).isNull(); } @Test // GH-5065 @DisplayName("Converter should read an empty object as an empty map when using @DocumentReference.") void readsEmptyMapWithDocumentReferenceCorrectly() { org.bson.Document document = org.bson.Document.parse("{\"map\":{}}"); - DocumentReferenceEmptyMapDocument target = converter.read(DocumentReferenceEmptyMapDocument.class, document); - assertThat(target.map).isNotNull().isEmpty(); + //DocumentReferenceEmptyMapDocument target = converter.read(DocumentReferenceEmptyMapDocument.class, document); + assertThat(converter.read(DocumentReferenceEmptyMapDocument.class, document).map).isNotNull().isEmpty(); + assertThat(converter.read(DocumentReferenceEmptyMapDocument.class, document).getMap()).isNotNull().isEmpty(); } @Test // GH-5065 - @DisplayName("Converter should read an empty object as an empty map with a valis values property when using @DocumentReference(lazy = true).") + @DisplayName("Converter should read an explicitly assigned null as a null map when using @DocumentReference.") + void readsExplicitlyNullMapWithDocumentReferenceCorrectly() { + org.bson.Document document = org.bson.Document.parse("{\"map\":null}"); + //DocumentReferenceEmptyMapDocument target = converter.read(DocumentReferenceEmptyMapDocument.class, document); + assertThat(converter.read(DocumentReferenceEmptyMapDocument.class, document).map).isNull(); + assertThat(converter.read(DocumentReferenceEmptyMapDocument.class, document).getMap()).isNull(); + } + + @Test // GH-5065 + @DisplayName("Converter should read an empty object as an empty map with a valid values property when using @DocumentReference(lazy = true).") void readsEmptyMapWithLazyLoadedDocumentReferenceCorrectly() { org.bson.Document document = org.bson.Document.parse("{\"map\":{}}"); - LazyDocumentReferenceEmptyMapDocument target = converter.read(LazyDocumentReferenceEmptyMapDocument.class, document); - assertThat(target.map).isNotNull(); - assertThat(target.map.values()).isNotNull(); + //LazyDocumentReferenceEmptyMapDocument target = converter.read(LazyDocumentReferenceEmptyMapDocument.class, document); + assertThat(converter.read(LazyDocumentReferenceEmptyMapDocument.class, document).map).isNotNull().isEmpty(); + assertThat(converter.read(LazyDocumentReferenceEmptyMapDocument.class, document).getMap()).isNotNull().isEmpty(); + assertThat(converter.read(LazyDocumentReferenceEmptyMapDocument.class, document).map.values()).isNotNull().isEmpty(); + assertThat(converter.read(LazyDocumentReferenceEmptyMapDocument.class, document).getMap().values()).isNotNull().isEmpty(); + } + + @Test // GH-5065 + @DisplayName("Converter should read an empty object as an empty map when using @DBRef.") + void readsEmptyMapWithDBRefCorrectly() { + org.bson.Document document = org.bson.Document.parse("{\"map\":{}}"); + //DBRefEmptyMapDocument target = converter.read(DBRefEmptyMapDocument.class, document); + assertThat(converter.read(DBRefEmptyMapDocument.class, document).map).isNotNull().isEmpty(); + assertThat(converter.read(DBRefEmptyMapDocument.class, document).getMap()).isNotNull().isEmpty(); + } + + @Test // GH-5065 + @DisplayName("Converter should read an explicitly assigned null as a null map when using @DBRef.") + void readsExplicitlyNullMapWithDBRefCorrectly() { + org.bson.Document document = org.bson.Document.parse("{\"map\":null}"); + //DBRefEmptyMapDocument target = converter.read(DBRefEmptyMapDocument.class, document); + assertThat(converter.read(DBRefEmptyMapDocument.class, document).map).isNull(); + assertThat(converter.read(DBRefEmptyMapDocument.class, document).getMap()).isNull(); } static class EmptyMapDocument { @@ -518,6 +558,10 @@ static class EmptyMapDocument { public EmptyMapDocument(Map map) { this.map = map; } + + Map getMap() { + return map; + } } static class DocumentReferenceEmptyMapDocument { @@ -528,6 +572,10 @@ static class DocumentReferenceEmptyMapDocument { public DocumentReferenceEmptyMapDocument(Map map) { this.map = map; } + + Map getMap() { + return map; + } } static class LazyDocumentReferenceEmptyMapDocument { @@ -538,6 +586,24 @@ static class LazyDocumentReferenceEmptyMapDocument { public LazyDocumentReferenceEmptyMapDocument(Map map) { this.map = map; } + + Map getMap() { + return map; + } + } + } + + static class DBRefEmptyMapDocument { + + @DBRef + Map map; + + public DBRefEmptyMapDocument(Map map) { + this.map = map; + } + + Map getMap() { + return map; } } From 7bbe289e2b5f0480898f01d4b108128bc35ff557 Mon Sep 17 00:00:00 2001 From: Shawn Kovalchick Date: Tue, 28 Oct 2025 09:49:26 -0400 Subject: [PATCH 4/8] Remove specific verify use occurrences and replace with atLeastOnce() Signed-off-by: Shawn Kovalchick --- .../DefaultDbRefResolverUnitTests.java | 31 ++++++++++--------- .../ReferenceLookupDelegateUnitTests.java | 24 +++++++++----- 2 files changed, 32 insertions(+), 23 deletions(-) diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolverUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolverUnitTests.java index ae54c8afa4..633d1477b2 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolverUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolverUnitTests.java @@ -164,15 +164,16 @@ void resolveEmptyMapIsNotNull() { Object target = resolver.resolveReference(property, source, lookupDelegate, entityReader); - verify(property, times(3)).isMap(); - verify(property, times(2)).isDocumentReference(); - verify(property, times(2)).getDocumentReference(); - verify(property, times(3)).isCollectionLike(); - verify(documentReference, times(1)).lookup(); - verify(documentReference, times(1)).sort(); - verify(documentReference, times(1)).lazy(); - verify(source, times(3)).getTargetSource(); + verify(property, atLeastOnce()).isMap(); + verify(property, atLeastOnce()).isDocumentReference(); + verify(property, atLeastOnce()).getDocumentReference(); + verify(property, atLeastOnce()).isCollectionLike(); + verify(documentReference, atLeastOnce()).lookup(); + verify(documentReference, atLeastOnce()).sort(); + verify(documentReference, atLeastOnce()).lazy(); + verify(source, atLeastOnce()).getTargetSource(); verifyNoMoreInteractions(documentReference, property, source); // Make sure we only call the properties we mocked. + assertThat(target) .isNotNull() .isInstanceOf(Map.class); @@ -200,13 +201,13 @@ void resolveLazyLoadedEmptyMapIsNotNull() { Object target = resolver.resolveReference(property, source, lookupDelegate, entityReader); - verify(property, times(1)).isMap(); - verify(property, times(1)).isDocumentReference(); - verify(property, times(1)).getDocumentReference(); - verify(property, times(1)).isCollectionLike(); - verify(property, times(1)).getType(); - verify(documentReference, times(1)).lazy(); - verify(source, times(1)).getTargetSource(); + verify(property, atLeastOnce()).isMap(); + verify(property, atLeastOnce()).isDocumentReference(); + verify(property, atLeastOnce()).getDocumentReference(); + verify(property, atLeastOnce()).isCollectionLike(); + verify(property, atLeastOnce()).getType(); + verify(documentReference, atLeastOnce()).lazy(); + verify(source, atLeastOnce()).getTargetSource(); verifyNoMoreInteractions(documentReference, property, source); // Make sure we only call the properties we mocked. assertThat(target) diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegateUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegateUnitTests.java index c7d9f3494a..715e766b77 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegateUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegateUnitTests.java @@ -111,18 +111,26 @@ void shouldResolveEmptyMapOnEmptyDocumentReferenceMapProperty() { when(property.isCollectionLike()).thenReturn(false); DocumentReferenceSource source = mock(DocumentReferenceSource.class); when(source.getTargetSource()).thenReturn(Document.parse("{}")); + + // Placeholder mock. It should never get called. ReferenceLookupDelegate.LookupFunction lookupFunction = mock(ReferenceLookupDelegate.LookupFunction.class); Object target = lookupDelegate.readReference(property, source, lookupFunction, entityReader); - verify(lookupFunction, never()).apply(any(), any()); // Since we mocked it, make sure it is never called. - verify(property, times(2)).isMap(); - verify(property, times(1)).isDocumentReference(); - verify(property, times(1)).getDocumentReference(); - verify(property, times(2)).isCollectionLike(); - verify(documentReference, times(1)).lookup(); - verify(documentReference, times(1)).sort(); - verifyNoMoreInteractions(documentReference, property, source); // Make sure we only call the properties we mocked. + // Since we mocked a placeholder that should never be called, make sure it is never called. + verify(lookupFunction, never()).apply(any(), any()); + + // Verify that all the mocks we created are used. + verify(property, atLeastOnce()).isMap(); + verify(property, atLeastOnce()).isDocumentReference(); + verify(property, atLeastOnce()).getDocumentReference(); + verify(property, atLeastOnce()).isCollectionLike(); + verify(documentReference, atLeastOnce()).lookup(); + verify(documentReference, atLeastOnce()).sort(); + + // Make sure we only call the properties we mocked. + verifyNoMoreInteractions(documentReference, property, source); + assertThat(target) .isNotNull() .isInstanceOf(Map.class); From aa213b49d5de418ddeac810becc4d35dd7c52d2b Mon Sep 17 00:00:00 2001 From: Shawn Kovalchick Date: Tue, 28 Oct 2025 09:57:11 -0400 Subject: [PATCH 5/8] Removed DisplayName annotations Signed-off-by: Shawn Kovalchick --- .../DefaultDbRefResolverUnitTests.java | 7 +-- .../convert/MappingMongoConverterTests.java | 52 +++---------------- .../ReferenceLookupDelegateUnitTests.java | 4 +- 3 files changed, 10 insertions(+), 53 deletions(-) diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolverUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolverUnitTests.java index 633d1477b2..d1ee651092 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolverUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolverUnitTests.java @@ -26,7 +26,6 @@ import org.bson.Document; import org.bson.types.ObjectId; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; @@ -145,8 +144,7 @@ void bulkFetchContainsDuplicates() { } @Test // GH-5065 - @DisplayName("GH-5065: Empty Map with @DocumentReference annotation should deserialize to an empty map.") - void resolveEmptyMapIsNotNull() { + void emptyMapWithDocumentReferenceAnnotationShouldDeserializeToAnEmptyMap() { DocumentReference documentReference = mock(DocumentReference.class); when(documentReference.lookup()).thenReturn("{ '_id' : ?#{#target} }"); when(documentReference.sort()).thenReturn(""); @@ -180,8 +178,7 @@ void resolveEmptyMapIsNotNull() { } @Test // GH-5065 - @DisplayName("GH-5065: Lazy loaded empty Map with @DocumentReference annotation should deserialize to an empty map with a non-null values property.") - void resolveLazyLoadedEmptyMapIsNotNull() { + void lazyLoadedEmptyMapWithDocumentReferenceAnnotationShouldDeserializeToAnEmptyMapWithANonnullValuesProperty() { DocumentReference documentReference = mock(DocumentReference.class); when(documentReference.lookup()).thenReturn("{ '_id' : ?#{#target} }"); when(documentReference.sort()).thenReturn(""); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterTests.java index a84aca5d3b..f706fa5028 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterTests.java @@ -34,7 +34,6 @@ import org.bson.Document; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -57,13 +56,13 @@ * @author Christoph Strobl */ -public class MappingMongoConverterTests { +class MappingMongoConverterTests { private static final String DATABASE = "mapping-converter-tests"; private static @Client MongoClient client; - private MongoDatabaseFactory factory = new SimpleMongoClientDatabaseFactory(client, DATABASE); + private final MongoDatabaseFactory factory = new SimpleMongoClientDatabaseFactory(client, DATABASE); private MappingMongoConverter converter; private MongoMappingContext mappingContext; @@ -483,30 +482,10 @@ public String toString() { } @Nested // GH-5065 - @DisplayName("GH-5065: Empty Map Tests") class EmptyMapTests { @Test // GH-5065 - @DisplayName("Test control: Passing test to indicate that the problem is not a more generic issue with maps in general.") - void readsEmptyMapCorrectly() { - org.bson.Document document = org.bson.Document.parse("{\"map\":{}}"); - //EmptyMapDocument target = converter.read(EmptyMapDocument.class, document); - assertThat(converter.read(EmptyMapDocument.class, document).map).isNotNull().isEmpty(); - assertThat(converter.read(EmptyMapDocument.class, document).getMap()).isNotNull().isEmpty(); - } - - @Test // GH-5065 - @DisplayName("Test control: Converter should read an explicitly assigned null as a null map.") - void readsExplicitlyNullMapCorrectly() { - org.bson.Document document = org.bson.Document.parse("{\"map\":null}"); - //EmptyMapDocument target = converter.read(EmptyMapDocument.class, document); - assertThat(converter.read(EmptyMapDocument.class, document).map).isNull(); - assertThat(converter.read(EmptyMapDocument.class, document).getMap()).isNull(); - } - - @Test // GH-5065 - @DisplayName("Converter should read an empty object as an empty map when using @DocumentReference.") - void readsEmptyMapWithDocumentReferenceCorrectly() { + void converterShouldReadAnEmptyObjectAsAnEmptyMapWhenUsingDocumentReferenceAnnotation() { org.bson.Document document = org.bson.Document.parse("{\"map\":{}}"); //DocumentReferenceEmptyMapDocument target = converter.read(DocumentReferenceEmptyMapDocument.class, document); assertThat(converter.read(DocumentReferenceEmptyMapDocument.class, document).map).isNotNull().isEmpty(); @@ -514,8 +493,7 @@ void readsEmptyMapWithDocumentReferenceCorrectly() { } @Test // GH-5065 - @DisplayName("Converter should read an explicitly assigned null as a null map when using @DocumentReference.") - void readsExplicitlyNullMapWithDocumentReferenceCorrectly() { + void converterShouldReadAnExplicitlyAssignedNullAsANullMapWhenUsingDocumentReferenceAnnotation() { org.bson.Document document = org.bson.Document.parse("{\"map\":null}"); //DocumentReferenceEmptyMapDocument target = converter.read(DocumentReferenceEmptyMapDocument.class, document); assertThat(converter.read(DocumentReferenceEmptyMapDocument.class, document).map).isNull(); @@ -523,8 +501,7 @@ void readsExplicitlyNullMapWithDocumentReferenceCorrectly() { } @Test // GH-5065 - @DisplayName("Converter should read an empty object as an empty map with a valid values property when using @DocumentReference(lazy = true).") - void readsEmptyMapWithLazyLoadedDocumentReferenceCorrectly() { + void converterShouldReadAnEmptyObjectAsAnEmptyMapWithAValidValuesPropertyWhenUsingDocumentReferenceAnnotationWithLazyEqualToTrue() { org.bson.Document document = org.bson.Document.parse("{\"map\":{}}"); //LazyDocumentReferenceEmptyMapDocument target = converter.read(LazyDocumentReferenceEmptyMapDocument.class, document); assertThat(converter.read(LazyDocumentReferenceEmptyMapDocument.class, document).map).isNotNull().isEmpty(); @@ -534,8 +511,7 @@ void readsEmptyMapWithLazyLoadedDocumentReferenceCorrectly() { } @Test // GH-5065 - @DisplayName("Converter should read an empty object as an empty map when using @DBRef.") - void readsEmptyMapWithDBRefCorrectly() { + void converterShouldReadAnEmptyObjectAsAnEmptyMapWhenUsingDBRefAnnotation() { org.bson.Document document = org.bson.Document.parse("{\"map\":{}}"); //DBRefEmptyMapDocument target = converter.read(DBRefEmptyMapDocument.class, document); assertThat(converter.read(DBRefEmptyMapDocument.class, document).map).isNotNull().isEmpty(); @@ -543,27 +519,13 @@ void readsEmptyMapWithDBRefCorrectly() { } @Test // GH-5065 - @DisplayName("Converter should read an explicitly assigned null as a null map when using @DBRef.") - void readsExplicitlyNullMapWithDBRefCorrectly() { + void converterShouldReadAnExplicitlyAssignedNullAsANullMapWhenUsingDBRefAnnotation() { org.bson.Document document = org.bson.Document.parse("{\"map\":null}"); //DBRefEmptyMapDocument target = converter.read(DBRefEmptyMapDocument.class, document); assertThat(converter.read(DBRefEmptyMapDocument.class, document).map).isNull(); assertThat(converter.read(DBRefEmptyMapDocument.class, document).getMap()).isNull(); } - static class EmptyMapDocument { - - Map map; - - public EmptyMapDocument(Map map) { - this.map = map; - } - - Map getMap() { - return map; - } - } - static class DocumentReferenceEmptyMapDocument { @DocumentReference diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegateUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegateUnitTests.java index 715e766b77..767bd15f93 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegateUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegateUnitTests.java @@ -23,7 +23,6 @@ import org.bson.Document; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -99,8 +98,7 @@ void shouldResolveEmptyMapOnEmptyTargetCollection() { } @Test // GH-5065 - @DisplayName("GH-5065: Empty Map with @DocumentReference annotation should deserialize to an empty map.") - void shouldResolveEmptyMapOnEmptyDocumentReferenceMapProperty() { + void emptyMapWithDocumentReferenceAnnotationShouldDeserializeToAnEmptyMap() { DocumentReference documentReference = mock(DocumentReference.class); when(documentReference.lookup()).thenReturn("{ '_id' : ?#{#target} }"); when(documentReference.sort()).thenReturn(""); From 7e18999b8df5cff357d7f1d3b178da41569c3906 Mon Sep 17 00:00:00 2001 From: Shawn Kovalchick Date: Tue, 28 Oct 2025 10:45:28 -0400 Subject: [PATCH 6/8] Splitting tests that might have side effects into separate tests Signed-off-by: Shawn Kovalchick --- .../convert/MappingMongoConverterTests.java | 76 ++++++++++++++----- 1 file changed, 59 insertions(+), 17 deletions(-) diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterTests.java index f706fa5028..c60af3c5b3 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterTests.java @@ -487,43 +487,85 @@ class EmptyMapTests { @Test // GH-5065 void converterShouldReadAnEmptyObjectAsAnEmptyMapWhenUsingDocumentReferenceAnnotation() { org.bson.Document document = org.bson.Document.parse("{\"map\":{}}"); - //DocumentReferenceEmptyMapDocument target = converter.read(DocumentReferenceEmptyMapDocument.class, document); - assertThat(converter.read(DocumentReferenceEmptyMapDocument.class, document).map).isNotNull().isEmpty(); - assertThat(converter.read(DocumentReferenceEmptyMapDocument.class, document).getMap()).isNotNull().isEmpty(); + DocumentReferenceEmptyMapDocument target = converter.read(DocumentReferenceEmptyMapDocument.class, document); + assertThat(target.map).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void converterShouldReadAnEmptyObjectAsAnEmptyMapWhenUsingDocumentReferenceAnnotationUsingGetMap() { + org.bson.Document document = org.bson.Document.parse("{\"map\":{}}"); + DocumentReferenceEmptyMapDocument target = converter.read(DocumentReferenceEmptyMapDocument.class, document); + assertThat(target.getMap()).isNotNull().isEmpty(); } @Test // GH-5065 void converterShouldReadAnExplicitlyAssignedNullAsANullMapWhenUsingDocumentReferenceAnnotation() { org.bson.Document document = org.bson.Document.parse("{\"map\":null}"); - //DocumentReferenceEmptyMapDocument target = converter.read(DocumentReferenceEmptyMapDocument.class, document); - assertThat(converter.read(DocumentReferenceEmptyMapDocument.class, document).map).isNull(); - assertThat(converter.read(DocumentReferenceEmptyMapDocument.class, document).getMap()).isNull(); + DocumentReferenceEmptyMapDocument target = converter.read(DocumentReferenceEmptyMapDocument.class, document); + assertThat(target.map).isNull(); + } + + @Test // GH-5065 + void converterShouldReadAnExplicitlyAssignedNullAsANullMapWhenUsingDocumentReferenceAnnotationUsingGetMap() { + org.bson.Document document = org.bson.Document.parse("{\"map\":null}"); + DocumentReferenceEmptyMapDocument target = converter.read(DocumentReferenceEmptyMapDocument.class, document); + assertThat(target.getMap()).isNull(); + } + + @Test // GH-5065 + void converterShouldReadAnEmptyObjectAsAnEmptyMapWhenUsingDocumentReferenceAnnotationWithLazyEqualToTrue() { + org.bson.Document document = org.bson.Document.parse("{\"map\":{}}"); + LazyDocumentReferenceEmptyMapDocument target = converter.read(LazyDocumentReferenceEmptyMapDocument.class, document); + assertThat(target.map).isNotNull().isEmpty(); } @Test // GH-5065 void converterShouldReadAnEmptyObjectAsAnEmptyMapWithAValidValuesPropertyWhenUsingDocumentReferenceAnnotationWithLazyEqualToTrue() { org.bson.Document document = org.bson.Document.parse("{\"map\":{}}"); - //LazyDocumentReferenceEmptyMapDocument target = converter.read(LazyDocumentReferenceEmptyMapDocument.class, document); - assertThat(converter.read(LazyDocumentReferenceEmptyMapDocument.class, document).map).isNotNull().isEmpty(); - assertThat(converter.read(LazyDocumentReferenceEmptyMapDocument.class, document).getMap()).isNotNull().isEmpty(); - assertThat(converter.read(LazyDocumentReferenceEmptyMapDocument.class, document).map.values()).isNotNull().isEmpty(); - assertThat(converter.read(LazyDocumentReferenceEmptyMapDocument.class, document).getMap().values()).isNotNull().isEmpty(); + LazyDocumentReferenceEmptyMapDocument target = converter.read(LazyDocumentReferenceEmptyMapDocument.class, document); + assertThat(target.map.values()).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void converterShouldReadAnEmptyObjectAsAnEmptyMapWhenUsingDocumentReferenceAnnotationWithLazyEqualToTrueUsingGetMap() { + org.bson.Document document = org.bson.Document.parse("{\"map\":{}}"); + LazyDocumentReferenceEmptyMapDocument target = converter.read(LazyDocumentReferenceEmptyMapDocument.class, document); + assertThat(target.getMap()).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void converterShouldReadAnEmptyObjectAsAnEmptyMapWithAValidValuesPropertyWhenUsingDocumentReferenceAnnotationWithLazyEqualToTrueUsingGetMap() { + org.bson.Document document = org.bson.Document.parse("{\"map\":{}}"); + LazyDocumentReferenceEmptyMapDocument target = converter.read(LazyDocumentReferenceEmptyMapDocument.class, document); + assertThat(target.getMap().values()).isNotNull().isEmpty(); } @Test // GH-5065 void converterShouldReadAnEmptyObjectAsAnEmptyMapWhenUsingDBRefAnnotation() { org.bson.Document document = org.bson.Document.parse("{\"map\":{}}"); - //DBRefEmptyMapDocument target = converter.read(DBRefEmptyMapDocument.class, document); - assertThat(converter.read(DBRefEmptyMapDocument.class, document).map).isNotNull().isEmpty(); - assertThat(converter.read(DBRefEmptyMapDocument.class, document).getMap()).isNotNull().isEmpty(); + DBRefEmptyMapDocument target = converter.read(DBRefEmptyMapDocument.class, document); + assertThat(target.map).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void converterShouldReadAnEmptyObjectAsAnEmptyMapWhenUsingDBRefAnnotationUsingGetMap() { + org.bson.Document document = org.bson.Document.parse("{\"map\":{}}"); + DBRefEmptyMapDocument target = converter.read(DBRefEmptyMapDocument.class, document); + assertThat(target.getMap()).isNotNull().isEmpty(); } @Test // GH-5065 void converterShouldReadAnExplicitlyAssignedNullAsANullMapWhenUsingDBRefAnnotation() { org.bson.Document document = org.bson.Document.parse("{\"map\":null}"); - //DBRefEmptyMapDocument target = converter.read(DBRefEmptyMapDocument.class, document); - assertThat(converter.read(DBRefEmptyMapDocument.class, document).map).isNull(); - assertThat(converter.read(DBRefEmptyMapDocument.class, document).getMap()).isNull(); + DBRefEmptyMapDocument target = converter.read(DBRefEmptyMapDocument.class, document); + assertThat(target.map).isNull(); + } + + @Test // GH-5065 + void converterShouldReadAnExplicitlyAssignedNullAsANullMapWhenUsingDBRefAnnotationUsingGetMap() { + org.bson.Document document = org.bson.Document.parse("{\"map\":null}"); + DBRefEmptyMapDocument target = converter.read(DBRefEmptyMapDocument.class, document); + assertThat(target.getMap()).isNull(); } static class DocumentReferenceEmptyMapDocument { From 852d5299151053e5a40fb999d51334c41bf3e4f8 Mon Sep 17 00:00:00 2001 From: Shawn Kovalchick Date: Wed, 29 Oct 2025 21:18:45 -0400 Subject: [PATCH 7/8] Remove interaction verifications Signed-off-by: Shawn Kovalchick --- .../DefaultDbRefResolverUnitTests.java | 19 ------------------- .../ReferenceLookupDelegateUnitTests.java | 15 --------------- 2 files changed, 34 deletions(-) diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolverUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolverUnitTests.java index d1ee651092..0add71d50e 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolverUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolverUnitTests.java @@ -162,16 +162,6 @@ void emptyMapWithDocumentReferenceAnnotationShouldDeserializeToAnEmptyMap() { Object target = resolver.resolveReference(property, source, lookupDelegate, entityReader); - verify(property, atLeastOnce()).isMap(); - verify(property, atLeastOnce()).isDocumentReference(); - verify(property, atLeastOnce()).getDocumentReference(); - verify(property, atLeastOnce()).isCollectionLike(); - verify(documentReference, atLeastOnce()).lookup(); - verify(documentReference, atLeastOnce()).sort(); - verify(documentReference, atLeastOnce()).lazy(); - verify(source, atLeastOnce()).getTargetSource(); - verifyNoMoreInteractions(documentReference, property, source); // Make sure we only call the properties we mocked. - assertThat(target) .isNotNull() .isInstanceOf(Map.class); @@ -198,15 +188,6 @@ void lazyLoadedEmptyMapWithDocumentReferenceAnnotationShouldDeserializeToAnEmpty Object target = resolver.resolveReference(property, source, lookupDelegate, entityReader); - verify(property, atLeastOnce()).isMap(); - verify(property, atLeastOnce()).isDocumentReference(); - verify(property, atLeastOnce()).getDocumentReference(); - verify(property, atLeastOnce()).isCollectionLike(); - verify(property, atLeastOnce()).getType(); - verify(documentReference, atLeastOnce()).lazy(); - verify(source, atLeastOnce()).getTargetSource(); - verifyNoMoreInteractions(documentReference, property, source); // Make sure we only call the properties we mocked. - assertThat(target) .isNotNull() .isInstanceOf(Map.class) diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegateUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegateUnitTests.java index 767bd15f93..8aecb4f027 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegateUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegateUnitTests.java @@ -45,7 +45,6 @@ class ReferenceLookupDelegateUnitTests { @Mock MappingContext, MongoPersistentProperty> mappingContext; @Mock SpELContext spELContext; - @Mock EvaluationContext evaluationContext; @Mock MongoEntityReader entityReader; private ReferenceLookupDelegate lookupDelegate; @@ -115,20 +114,6 @@ void emptyMapWithDocumentReferenceAnnotationShouldDeserializeToAnEmptyMap() { Object target = lookupDelegate.readReference(property, source, lookupFunction, entityReader); - // Since we mocked a placeholder that should never be called, make sure it is never called. - verify(lookupFunction, never()).apply(any(), any()); - - // Verify that all the mocks we created are used. - verify(property, atLeastOnce()).isMap(); - verify(property, atLeastOnce()).isDocumentReference(); - verify(property, atLeastOnce()).getDocumentReference(); - verify(property, atLeastOnce()).isCollectionLike(); - verify(documentReference, atLeastOnce()).lookup(); - verify(documentReference, atLeastOnce()).sort(); - - // Make sure we only call the properties we mocked. - verifyNoMoreInteractions(documentReference, property, source); - assertThat(target) .isNotNull() .isInstanceOf(Map.class); From fbdb74665590fa9c8abd8a94231f273c86af27c1 Mon Sep 17 00:00:00 2001 From: Shawn Kovalchick Date: Wed, 29 Oct 2025 22:08:01 -0400 Subject: [PATCH 8/8] Duplicated the map tests for lists and sets Added back in the control tests Signed-off-by: Shawn Kovalchick --- .../convert/MappingMongoConverterTests.java | 359 +++++++++++++++++- .../ReferenceLookupDelegateUnitTests.java | 1 - 2 files changed, 351 insertions(+), 9 deletions(-) diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterTests.java index c60af3c5b3..696acd69f5 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterTests.java @@ -482,7 +482,23 @@ public String toString() { } @Nested // GH-5065 - class EmptyMapTests { + class EmptyMapTests { + + @Test // GH-5065 + void controlTestToIllustrateThatTheEmptyMapProblemIsLimitedToDocumentReferencesAndNotAMoreGenericProblem() { + org.bson.Document document = org.bson.Document.parse("{\"map\":{}}"); + //EmptyMapDocument target = converter.read(EmptyMapDocument.class, document); + assertThat(converter.read(EmptyMapDocument.class, document).map).isNotNull().isEmpty(); + assertThat(converter.read(EmptyMapDocument.class, document).getMap()).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void controlTestToIllustrateThatTheNullMapProblemIsLimitedToDocumentReferencesAndNotAMoreGenericProblem() { + org.bson.Document document = org.bson.Document.parse("{\"map\":null}"); + //EmptyMapDocument target = converter.read(EmptyMapDocument.class, document); + assertThat(converter.read(EmptyMapDocument.class, document).map).isNull(); + assertThat(converter.read(EmptyMapDocument.class, document).getMap()).isNull(); + } @Test // GH-5065 void converterShouldReadAnEmptyObjectAsAnEmptyMapWhenUsingDocumentReferenceAnnotation() { @@ -568,6 +584,19 @@ void converterShouldReadAnExplicitlyAssignedNullAsANullMapWhenUsingDBRefAnnotati assertThat(target.getMap()).isNull(); } + static class EmptyMapDocument { + + Map map; + + public EmptyMapDocument(Map map) { + this.map = map; + } + + Map getMap() { + return map; + } + } + static class DocumentReferenceEmptyMapDocument { @DocumentReference @@ -595,19 +624,333 @@ Map getMap() { return map; } } + + static class DBRefEmptyMapDocument { + + @DBRef + Map map; + + public DBRefEmptyMapDocument(Map map) { + this.map = map; + } + + Map getMap() { + return map; + } + } } - static class DBRefEmptyMapDocument { + @Nested // GH-5065 + class EmptyListTests { + + @Test // GH-5065 + void controlTestToIllustrateThatTheEmptyListProblemIsLimitedToDocumentReferencesAndNotAMoreGenericProblem() { + org.bson.Document document = org.bson.Document.parse("{\"list\":[]}"); + EmptyListDocument target = converter.read(EmptyListDocument.class, document); + assertThat(target.list).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void controlTestToIllustrateThatTheEmptyListProblemIsLimitedToDocumentReferencesAndNotAMoreGenericProblemUsingGetList() { + org.bson.Document document = org.bson.Document.parse("{\"list\":[]}"); + EmptyListDocument target = converter.read(EmptyListDocument.class, document); + assertThat(target.getList()).isNotNull().isEmpty(); + } - @DBRef - Map map; + @Test // GH-5065 + void controlTestToIllustrateThatTheNullListProblemIsLimitedToDocumentReferencesAndNotAMoreGenericProblem() { + org.bson.Document document = org.bson.Document.parse("{\"list\":null}"); + EmptyListDocument target = converter.read(EmptyListDocument.class, document); + assertThat(target.list).isNull(); + } - public DBRefEmptyMapDocument(Map map) { - this.map = map; + @Test // GH-5065 + void controlTestToIllustrateThatTheNullListProblemIsLimitedToDocumentReferencesAndNotAMoreGenericProblemUsingGetList() { + org.bson.Document document = org.bson.Document.parse("{\"list\":null}"); + EmptyListDocument target = converter.read(EmptyListDocument.class, document); + assertThat(target.getList()).isNull(); } - Map getMap() { - return map; + @Test // GH-5065 + void converterShouldReadAnEmptyObjectAsAnEmptyListWhenUsingDocumentReferenceAnnotation() { + org.bson.Document document = org.bson.Document.parse("{\"list\":[]}"); + DocumentReferenceEmptyListDocument target = converter.read(DocumentReferenceEmptyListDocument.class, document); + assertThat(target.list).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void converterShouldReadAnEmptyObjectAsAnEmptyListWhenUsingDocumentReferenceAnnotationUsingGetList() { + org.bson.Document document = org.bson.Document.parse("{\"list\":[]}"); + DocumentReferenceEmptyListDocument target = converter.read(DocumentReferenceEmptyListDocument.class, document); + assertThat(target.getList()).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void converterShouldReadAnExplicitlyAssignedNullAsANullListWhenUsingDocumentReferenceAnnotation() { + org.bson.Document document = org.bson.Document.parse("{\"list\":null}"); + DocumentReferenceEmptyListDocument target = converter.read(DocumentReferenceEmptyListDocument.class, document); + assertThat(target.list).isNull(); + } + + @Test // GH-5065 + void converterShouldReadAnExplicitlyAssignedNullAsANullListWhenUsingDocumentReferenceAnnotationUsingGetList() { + org.bson.Document document = org.bson.Document.parse("{\"list\":null}"); + DocumentReferenceEmptyListDocument target = converter.read(DocumentReferenceEmptyListDocument.class, document); + assertThat(target.getList()).isNull(); + } + + @Test // GH-5065 + void converterShouldReadAnEmptyObjectAsAnEmptyListWhenUsingDocumentReferenceAnnotationWithLazyEqualToTrue() { + org.bson.Document document = org.bson.Document.parse("{\"list\":[]}"); + LazyDocumentReferenceEmptyListDocument target = converter.read(LazyDocumentReferenceEmptyListDocument.class, document); + assertThat(target.list).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void converterShouldReadAnEmptyObjectAsAnEmptyListWhenUsingDocumentReferenceAnnotationWithLazyEqualToTrueUsingGetList() { + org.bson.Document document = org.bson.Document.parse("{\"list\":[]}"); + LazyDocumentReferenceEmptyListDocument target = converter.read(LazyDocumentReferenceEmptyListDocument.class, document); + assertThat(target.getList()).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void converterShouldReadAnEmptyObjectAsAnEmptyListWhenUsingDBRefAnnotation() { + org.bson.Document document = org.bson.Document.parse("{\"list\":[]}"); + DBRefEmptyListDocument target = converter.read(DBRefEmptyListDocument.class, document); + assertThat(target.list).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void converterShouldReadAnEmptyObjectAsAnEmptyListWhenUsingDBRefAnnotationUsingGetList() { + org.bson.Document document = org.bson.Document.parse("{\"list\":[]}"); + DBRefEmptyListDocument target = converter.read(DBRefEmptyListDocument.class, document); + assertThat(target.getList()).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void converterShouldReadAnExplicitlyAssignedNullAsANullListWhenUsingDBRefAnnotation() { + org.bson.Document document = org.bson.Document.parse("{\"list\":null}"); + DBRefEmptyListDocument target = converter.read(DBRefEmptyListDocument.class, document); + assertThat(target.list).isNull(); + } + + @Test // GH-5065 + void converterShouldReadAnExplicitlyAssignedNullAsANullListWhenUsingDBRefAnnotationUsingGetList() { + org.bson.Document document = org.bson.Document.parse("{\"list\":null}"); + DBRefEmptyListDocument target = converter.read(DBRefEmptyListDocument.class, document); + assertThat(target.getList()).isNull(); + } + + static class EmptyListDocument { + + List list; + + public EmptyListDocument(List list) { + this.list = list; + } + + List getList() { + return list; + } + } + + static class DocumentReferenceEmptyListDocument { + + @DocumentReference + List list; + + public DocumentReferenceEmptyListDocument(List list) { + this.list = list; + } + + List getList() { + return list; + } + } + + static class LazyDocumentReferenceEmptyListDocument { + + @DocumentReference(lazy = true) + List list; + + public LazyDocumentReferenceEmptyListDocument(List list) { + this.list = list; + } + + List getList() { + return list; + } + } + + static class DBRefEmptyListDocument { + + @DBRef + List list; + + public DBRefEmptyListDocument(List list) { + this.list = list; + } + + List getList() { + return list; + } + } + } + + @Nested // GH-5065 + class EmptySetTests { + + @Test // GH-5065 + void controlTestToIllustrateThatTheEmptySetProblemIsLimitedToDocumentReferencesAndNotAMoreGenericProblem() { + org.bson.Document document = org.bson.Document.parse("{\"set\":[]}"); + EmptySetDocument target = converter.read(EmptySetDocument.class, document); + assertThat(target.set).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void controlTestToIllustrateThatTheEmptySetProblemIsLimitedToDocumentReferencesAndNotAMoreGenericProblemUsingGetSet() { + org.bson.Document document = org.bson.Document.parse("{\"set\":[]}"); + EmptySetDocument target = converter.read(EmptySetDocument.class, document); + assertThat(target.getSet()).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void controlTestToIllustrateThatTheNullSetProblemIsLimitedToDocumentReferencesAndNotAMoreGenericProblem() { + org.bson.Document document = org.bson.Document.parse("{\"set\":null}"); + EmptySetDocument target = converter.read(EmptySetDocument.class, document); + assertThat(target.set).isNull(); + } + + @Test // GH-5065 + void controlTestToIllustrateThatTheNullSetProblemIsLimitedToDocumentReferencesAndNotAMoreGenericProblemUsingGetSet() { + org.bson.Document document = org.bson.Document.parse("{\"set\":null}"); + EmptySetDocument target = converter.read(EmptySetDocument.class, document); + assertThat(target.getSet()).isNull(); + } + + @Test // GH-5065 + void converterShouldReadAnEmptyObjectAsAnEmptySetWhenUsingDocumentReferenceAnnotation() { + org.bson.Document document = org.bson.Document.parse("{\"set\":[]}"); + DocumentReferenceEmptySetDocument target = converter.read(DocumentReferenceEmptySetDocument.class, document); + assertThat(target.set).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void converterShouldReadAnEmptyObjectAsAnEmptySetWhenUsingDocumentReferenceAnnotationUsingGetSet() { + org.bson.Document document = org.bson.Document.parse("{\"set\":[]}"); + DocumentReferenceEmptySetDocument target = converter.read(DocumentReferenceEmptySetDocument.class, document); + assertThat(target.getSet()).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void converterShouldReadAnExplicitlyAssignedNullAsANullSetWhenUsingDocumentReferenceAnnotation() { + org.bson.Document document = org.bson.Document.parse("{\"set\":null}"); + DocumentReferenceEmptySetDocument target = converter.read(DocumentReferenceEmptySetDocument.class, document); + assertThat(target.set).isNull(); + } + + @Test // GH-5065 + void converterShouldReadAnExplicitlyAssignedNullAsANullSetWhenUsingDocumentReferenceAnnotationUsingGetSet() { + org.bson.Document document = org.bson.Document.parse("{\"set\":null}"); + DocumentReferenceEmptySetDocument target = converter.read(DocumentReferenceEmptySetDocument.class, document); + assertThat(target.getSet()).isNull(); + } + + @Test // GH-5065 + void converterShouldReadAnEmptyObjectAsAnEmptySetWhenUsingDocumentReferenceAnnotationWithLazyEqualToTrue() { + org.bson.Document document = org.bson.Document.parse("{\"set\":[]}"); + LazyDocumentReferenceEmptySetDocument target = converter.read(LazyDocumentReferenceEmptySetDocument.class, document); + assertThat(target.set).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void converterShouldReadAnEmptyObjectAsAnEmptySetWhenUsingDocumentReferenceAnnotationWithLazyEqualToTrueUsingGetSet() { + org.bson.Document document = org.bson.Document.parse("{\"set\":[]}"); + LazyDocumentReferenceEmptySetDocument target = converter.read(LazyDocumentReferenceEmptySetDocument.class, document); + assertThat(target.getSet()).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void converterShouldReadAnEmptyObjectAsAnEmptySetWhenUsingDBRefAnnotation() { + org.bson.Document document = org.bson.Document.parse("{\"set\":[]}"); + DBRefEmptySetDocument target = converter.read(DBRefEmptySetDocument.class, document); + assertThat(target.set).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void converterShouldReadAnEmptyObjectAsAnEmptySetWhenUsingDBRefAnnotationUsingGetSet() { + org.bson.Document document = org.bson.Document.parse("{\"set\":[]}"); + DBRefEmptySetDocument target = converter.read(DBRefEmptySetDocument.class, document); + assertThat(target.getSet()).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void converterShouldReadAnExplicitlyAssignedNullAsANullSetWhenUsingDBRefAnnotation() { + org.bson.Document document = org.bson.Document.parse("{\"set\":null}"); + DBRefEmptySetDocument target = converter.read(DBRefEmptySetDocument.class, document); + assertThat(target.set).isNull(); + } + + @Test // GH-5065 + void converterShouldReadAnExplicitlyAssignedNullAsANullSetWhenUsingDBRefAnnotationUsingGetSet() { + org.bson.Document document = org.bson.Document.parse("{\"set\":null}"); + DBRefEmptySetDocument target = converter.read(DBRefEmptySetDocument.class, document); + assertThat(target.getSet()).isNull(); + } + + static class EmptySetDocument { + + Set set; + + public EmptySetDocument(Set set) { + this.set = set; + } + + Set getSet() { + return set; + } + } + + static class DocumentReferenceEmptySetDocument { + + @DocumentReference + Set set; + + public DocumentReferenceEmptySetDocument(Set set) { + this.set = set; + } + + Set getSet() { + return set; + } + } + + static class LazyDocumentReferenceEmptySetDocument { + + @DocumentReference(lazy = true) + Set set; + + public LazyDocumentReferenceEmptySetDocument(Set set) { + this.set = set; + } + + Set getSet() { + return set; + } + } + + static class DBRefEmptySetDocument { + + @DBRef + Set set; + + public DBRefEmptySetDocument(Set set) { + this.set = set; + } + + Set getSet() { + return set; + } } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegateUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegateUnitTests.java index 8aecb4f027..a1eecab673 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegateUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegateUnitTests.java @@ -33,7 +33,6 @@ import org.springframework.data.mongodb.core.mapping.DocumentReference; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; -import org.springframework.expression.EvaluationContext; /** * Unit tests for {@link ReferenceLookupDelegate}.