Browse Source

Introduce tests for PropertyDescriptorUtils with bounded generics

As a follow up to commit 4b07edbaeb, this commit introduces tests for
PropertyDescriptorUtils.determineBasicProperties() using types with
bounded generics.

Note, however, that the following test effectively fails, since
PropertyDescriptorUtils.determineBasicProperties() does not match the
behavior of java.beans.Introspector. Consequently, this test method
currently changes the expected write method type conditionally.

resolvePropertiesWithUnresolvedGenericsInSubclassWithOverloadedSetter()

See gh-36019
pull/36101/head
Sam Brannen 4 weeks ago
parent
commit
e7f019bd3f
  1. 194
      spring-beans/src/test/java/org/springframework/beans/PropertyDescriptorUtilsPropertyResolutionTests.java

194
spring-beans/src/test/java/org/springframework/beans/PropertyDescriptorUtilsPropertyResolutionTests.java

@ -18,12 +18,14 @@ package org.springframework.beans; @@ -18,12 +18,14 @@ package org.springframework.beans;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import org.junit.jupiter.api.Named;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.Parameter;
import org.junit.jupiter.params.ParameterizedClass;
@ -58,58 +60,118 @@ class PropertyDescriptorUtilsPropertyResolutionTests { @@ -58,58 +60,118 @@ class PropertyDescriptorUtilsPropertyResolutionTests {
PropertiesResolver resolver;
@Test
void determineBasicPropertiesWithUnresolvedGenericsInInterface() {
var pdMap = resolver.resolve(GenericService.class);
@Nested
class UnboundedGenericsTests {
assertThat(pdMap).containsOnlyKeys("id");
assertReadAndWriteMethodsForId(pdMap.get("id"), Object.class, Object.class);
}
@Test
void determineBasicPropertiesWithUnresolvedGenericsInInterface() {
var pdMap = resolver.resolve(GenericService.class);
@Test
void determineBasicPropertiesWithUnresolvedGenericsInSubInterface() {
// FYI: java.beans.Introspector does not resolve properties for sub-interfaces.
assumeThat(resolver).isNotInstanceOf(StandardPropertiesResolver.class);
assertThat(pdMap).containsOnlyKeys("id");
assertReadAndWriteMethodsForId(pdMap.get("id"), Object.class, Object.class);
}
var pdMap = resolver.resolve(SubGenericService.class);
@Test
void determineBasicPropertiesWithUnresolvedGenericsInSubInterface() {
// FYI: java.beans.Introspector does not resolve properties for sub-interfaces.
assumeThat(resolver).isNotInstanceOf(StandardPropertiesResolver.class);
assertThat(pdMap).containsOnlyKeys("id");
assertReadAndWriteMethodsForId(pdMap.get("id"), Object.class, Object.class);
}
var pdMap = resolver.resolve(SubGenericService.class);
@Test
void resolvePropertiesWithUnresolvedGenericsInClass() {
var pdMap = resolver.resolve(BaseService.class);
assertThat(pdMap).containsOnlyKeys("id");
assertReadAndWriteMethodsForId(pdMap.get("id"), Object.class, Object.class);
}
assertReadAndWriteMethodsForClassAndId(pdMap, Object.class, Object.class);
}
@Test
void resolvePropertiesWithUnresolvedGenericsInClass() {
var pdMap = resolver.resolve(BaseService.class);
@Test // gh-36019
void resolvePropertiesInSubclassWithOverriddenGetterAndSetter() {
var pdMap = resolver.resolve(ServiceWithOverriddenGetterAndSetter.class);
assertReadAndWriteMethodsForClassAndId(pdMap, Object.class, Object.class);
}
assertReadAndWriteMethodsForClassAndId(pdMap, String.class, String.class);
}
@Test // gh-36019
void resolvePropertiesInSubclassWithOverriddenGetterAndSetter() {
var pdMap = resolver.resolve(ServiceWithOverriddenGetterAndSetter.class);
@Test // gh-36019
void resolvePropertiesWithUnresolvedGenericsInSubclassWithOverloadedSetter() {
var pdMap = resolver.resolve(ServiceWithOverloadedSetter.class);
assertReadAndWriteMethodsForClassAndId(pdMap, String.class, String.class);
}
assertReadAndWriteMethodsForClassAndId(pdMap, Object.class, Object.class);
}
@Test // gh-36019
void resolvePropertiesWithUnresolvedGenericsInSubclassWithOverloadedSetter() {
var pdMap = resolver.resolve(ServiceWithOverloadedSetter.class);
@Test // gh-36019
void resolvePropertiesWithPartiallyUnresolvedGenericsInSubclassWithOverriddenGetter() {
var pdMap = resolver.resolve(ServiceWithOverriddenGetter.class);
assertReadAndWriteMethodsForClassAndId(pdMap, Object.class, Object.class);
}
@Test // gh-36019
void resolvePropertiesWithPartiallyUnresolvedGenericsInSubclassWithOverriddenGetter() {
var pdMap = resolver.resolve(ServiceWithOverriddenGetter.class);
assertReadAndWriteMethodsForClassAndId(pdMap, String.class, Object.class);
}
@Test // gh-36019
void resolvePropertiesWithPartiallyUnresolvedGenericsInSubclassWithOverriddenGetterAndOverloadedSetter() {
var pdMap = resolver.resolve(ServiceWithOverriddenGetterAndOverloadedSetter.class);
assertReadAndWriteMethodsForClassAndId(pdMap, String.class, Object.class);
assertReadAndWriteMethodsForClassAndId(pdMap, String.class, Object.class);
}
}
@Test // gh-36019
void resolvePropertiesWithPartiallyUnresolvedGenericsInSubclassWithOverriddenGetterAndOverloadedSetter() {
var pdMap = resolver.resolve(ServiceWithOverriddenGetterAndOverloadedSetter.class);
@Nested
class BoundedGenericsTests {
@Test
void determineBasicPropertiesWithUnresolvedGenericsInInterface() {
var pdMap = resolver.resolve(Entity.class);
assertThat(pdMap).containsOnlyKeys("id");
assertReadAndWriteMethodsForId(pdMap.get("id"), Serializable.class, Serializable.class);
}
assertReadAndWriteMethodsForClassAndId(pdMap, String.class, Object.class);
@Test
void resolvePropertiesWithUnresolvedGenericsInClass() {
var pdMap = resolver.resolve(BaseEntity.class);
assertReadAndWriteMethodsForClassAndId(pdMap, Number.class, Number.class);
}
@Test
void resolvePropertiesWithUnresolvedGenericsInSubclass() {
var pdMap = resolver.resolve(Person.class);
assertReadAndWriteMethodsForClassAndId(pdMap, Number.class, Number.class);
}
@Test // gh-36019
void resolvePropertiesWithUnresolvedGenericsInSubclassWithOverriddenGetter() {
var pdMap = resolver.resolve(PersonWithOverriddenGetter.class);
assertReadAndWriteMethodsForClassAndId(pdMap, Long.class, Number.class);
}
@Test // gh-36019
void resolvePropertiesWithUnresolvedGenericsInSubclassWithOverriddenSetter() {
var pdMap = resolver.resolve(PersonWithOverriddenSetter.class);
assertReadAndWriteMethodsForClassAndId(pdMap, Number.class, Long.class);
}
@Test // gh-36019
void resolvePropertiesWithUnresolvedGenericsInSubclassWithOverloadedSetter() {
var pdMap = resolver.resolve(PersonWithOverloadedSetter.class);
// TODO Determine if we want to align PropertyDescriptorUtils with java.beans.Introspector.
Class<?> writeType = Number.class;
if (resolver instanceof BasicPropertiesResolver) {
// PropertyDescriptorUtils currently incorrectly resolves setId(Integer)
// as the write method instead of setId(Number) (where Number is the
// unresolved generic for Long).
writeType = Integer.class;
}
assertReadAndWriteMethodsForClassAndId(pdMap, Number.class, writeType);
}
}
@ -262,4 +324,62 @@ class PropertyDescriptorUtilsPropertyResolutionTests { @@ -262,4 +324,62 @@ class PropertyDescriptorUtilsPropertyResolutionTests {
}
}
interface Entity<T extends Serializable> {
T getId();
void setId(T id);
}
abstract static class BaseEntity<T extends Number> implements Entity<T> {
private T id;
@Override
public T getId() {
return this.id;
}
@Override
public void setId(T id) {
this.id = id;
}
}
static class Person extends BaseEntity<Long> {
}
static class PersonWithOverriddenGetter extends BaseEntity<Long> {
/**
* Overrides super implementation to ensure that the JavaBeans read method
* is of type {@link Long}, while leaving the type for the write method
* ({@link #setId}) set to {@link Number}.
*/
@Override
public Long getId() {
return super.getId();
}
}
static class PersonWithOverriddenSetter extends BaseEntity<Long> {
/**
* Overrides super implementation to ensure that the JavaBeans write method
* is of type {@link Long}, while leaving the type for the read method
* ({@link #getId()}) set to {@link Number}.
*/
@Override
public void setId(Long id) {
super.setId(id);
}
}
static class PersonWithOverloadedSetter extends BaseEntity<Long> {
// Intentionally chose Integer, since it's a subtype of Long and Number.
public void setId(Integer id) {
setId(id.longValue());
}
}
}

Loading…
Cancel
Save