From 93bc022a4684ea1d4581e86d180974145292e117 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Mon, 15 Dec 2025 18:02:17 +0100 Subject: [PATCH] Extract CopyPropertiesTests as nested test class in BeanUtilsTests --- .../springframework/beans/BeanUtilsTests.java | 552 +++++++++--------- 1 file changed, 279 insertions(+), 273 deletions(-) diff --git a/spring-beans/src/test/java/org/springframework/beans/BeanUtilsTests.java b/spring-beans/src/test/java/org/springframework/beans/BeanUtilsTests.java index 9eb400830ea..ad1fe6f2063 100644 --- a/spring-beans/src/test/java/org/springframework/beans/BeanUtilsTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/BeanUtilsTests.java @@ -34,6 +34,7 @@ import java.util.Locale; import java.util.UUID; import org.jspecify.annotations.Nullable; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; @@ -67,14 +68,14 @@ class BeanUtilsTests { @Test void instantiateClassGivenInterface() { - assertThatExceptionOfType(FatalBeanException.class).isThrownBy(() -> - BeanUtils.instantiateClass(List.class)); + assertThatExceptionOfType(FatalBeanException.class) + .isThrownBy(() -> BeanUtils.instantiateClass(List.class)); } @Test void instantiateClassGivenClassWithoutDefaultConstructor() { - assertThatExceptionOfType(FatalBeanException.class).isThrownBy(() -> - BeanUtils.instantiateClass(CustomDateEditor.class)); + assertThatExceptionOfType(FatalBeanException.class) + .isThrownBy(() -> BeanUtils.instantiateClass(CustomDateEditor.class)); } @Test // gh-22531 @@ -91,16 +92,16 @@ class BeanUtilsTests { void instantiateClassWithFewerArgsThanParameters() throws NoSuchMethodException { Constructor constructor = getBeanWithPrimitiveTypesConstructor(); - assertThatExceptionOfType(BeanInstantiationException.class).isThrownBy(() -> - BeanUtils.instantiateClass(constructor, null, null, "foo")); + assertThatExceptionOfType(BeanInstantiationException.class) + .isThrownBy(() -> BeanUtils.instantiateClass(constructor, null, null, "foo")); } @Test // gh-22531 void instantiateClassWithMoreArgsThanParameters() throws NoSuchMethodException { Constructor constructor = getBeanWithPrimitiveTypesConstructor(); - assertThatExceptionOfType(BeanInstantiationException.class).isThrownBy(() -> - BeanUtils.instantiateClass(constructor, null, null, null, null, null, null, null, null, "foo", null)); + assertThatExceptionOfType(BeanInstantiationException.class) + .isThrownBy(() -> BeanUtils.instantiateClass(constructor, null, null, null, null, null, null, null, null, "foo", null)); } @Test // gh-22531, gh-27390 @@ -157,267 +158,6 @@ class BeanUtilsTests { assertThat(BeanUtils.findEditorByConvention(Resource.class).getClass()).isEqualTo(ResourceEditor.class); } - @Test - void copyProperties() throws Exception { - TestBean tb = new TestBean(); - tb.setName("rod"); - tb.setAge(32); - tb.setTouchy("touchy"); - TestBean tb2 = new TestBean(); - assertThat(tb2.getName()).as("Name empty").isNull(); - assertThat(tb2.getAge()).as("Age empty").isEqualTo(0); - assertThat(tb2.getTouchy()).as("Touchy empty").isNull(); - BeanUtils.copyProperties(tb, tb2); - assertThat(tb2.getName()).as("Name copied").isEqualTo(tb.getName()); - assertThat(tb2.getAge()).as("Age copied").isEqualTo(tb.getAge()); - assertThat(tb2.getTouchy()).as("Touchy copied").isEqualTo(tb.getTouchy()); - } - - @Test - void copyPropertiesWithDifferentTypes1() throws Exception { - DerivedTestBean tb = new DerivedTestBean(); - tb.setName("rod"); - tb.setAge(32); - tb.setTouchy("touchy"); - TestBean tb2 = new TestBean(); - assertThat(tb2.getName()).as("Name empty").isNull(); - assertThat(tb2.getAge()).as("Age empty").isEqualTo(0); - assertThat(tb2.getTouchy()).as("Touchy empty").isNull(); - BeanUtils.copyProperties(tb, tb2); - assertThat(tb2.getName()).as("Name copied").isEqualTo(tb.getName()); - assertThat(tb2.getAge()).as("Age copied").isEqualTo(tb.getAge()); - assertThat(tb2.getTouchy()).as("Touchy copied").isEqualTo(tb.getTouchy()); - } - - @Test - void copyPropertiesWithDifferentTypes2() throws Exception { - TestBean tb = new TestBean(); - tb.setName("rod"); - tb.setAge(32); - tb.setTouchy("touchy"); - DerivedTestBean tb2 = new DerivedTestBean(); - assertThat(tb2.getName()).as("Name empty").isNull(); - assertThat(tb2.getAge()).as("Age empty").isEqualTo(0); - assertThat(tb2.getTouchy()).as("Touchy empty").isNull(); - BeanUtils.copyProperties(tb, tb2); - assertThat(tb2.getName()).as("Name copied").isEqualTo(tb.getName()); - assertThat(tb2.getAge()).as("Age copied").isEqualTo(tb.getAge()); - assertThat(tb2.getTouchy()).as("Touchy copied").isEqualTo(tb.getTouchy()); - } - - /** - * {@code Integer} can be copied to {@code Number}. - */ - @Test - void copyPropertiesFromSubTypeToSuperType() { - IntegerHolder integerHolder = new IntegerHolder(); - integerHolder.setNumber(42); - NumberHolder numberHolder = new NumberHolder(); - - BeanUtils.copyProperties(integerHolder, numberHolder); - assertThat(integerHolder.getNumber()).isEqualTo(42); - assertThat(numberHolder.getNumber()).isEqualTo(42); - } - - /** - * {@code List} can be copied to {@code List}. - */ - @Test - void copyPropertiesHonorsGenericTypeMatchesFromIntegerToInteger() { - IntegerListHolder1 integerListHolder1 = new IntegerListHolder1(); - integerListHolder1.getList().add(42); - IntegerListHolder2 integerListHolder2 = new IntegerListHolder2(); - - BeanUtils.copyProperties(integerListHolder1, integerListHolder2); - assertThat(integerListHolder1.getList()).containsExactly(42); - assertThat(integerListHolder2.getList()).containsExactly(42); - } - - /** - * {@code List} can be copied to {@code List}. - */ - @Test - void copyPropertiesHonorsGenericTypeMatchesFromWildcardToWildcard() { - List list = List.of("foo", 42); - WildcardListHolder1 wildcardListHolder1 = new WildcardListHolder1(); - wildcardListHolder1.setList(list); - WildcardListHolder2 wildcardListHolder2 = new WildcardListHolder2(); - assertThat(wildcardListHolder2.getList()).isEmpty(); - - BeanUtils.copyProperties(wildcardListHolder1, wildcardListHolder2); - assertThat(wildcardListHolder1.getList()).isEqualTo(list); - assertThat(wildcardListHolder2.getList()).isEqualTo(list); - } - - /** - * {@code List} can be copied to {@code List}. - */ - @Test - void copyPropertiesHonorsGenericTypeMatchesFromIntegerToWildcard() { - IntegerListHolder1 integerListHolder1 = new IntegerListHolder1(); - integerListHolder1.getList().add(42); - WildcardListHolder2 wildcardListHolder2 = new WildcardListHolder2(); - - BeanUtils.copyProperties(integerListHolder1, wildcardListHolder2); - assertThat(integerListHolder1.getList()).containsExactly(42); - assertThat(wildcardListHolder2.getList()).isEqualTo(List.of(42)); - } - - /** - * {@code List} can be copied to {@code List}. - */ - @Test - void copyPropertiesHonorsGenericTypeMatchesForUpperBoundedWildcard() { - IntegerListHolder1 integerListHolder1 = new IntegerListHolder1(); - integerListHolder1.getList().add(42); - NumberUpperBoundedWildcardListHolder numberListHolder = new NumberUpperBoundedWildcardListHolder(); - - BeanUtils.copyProperties(integerListHolder1, numberListHolder); - assertThat(integerListHolder1.getList()).containsExactly(42); - assertThat(numberListHolder.getList()).isEqualTo(List.of(42)); - } - - /** - * {@code Number} can NOT be copied to {@code Integer}. - */ - @Test - void copyPropertiesDoesNotCopyFromSuperTypeToSubType() { - NumberHolder numberHolder = new NumberHolder(); - numberHolder.setNumber(42); - IntegerHolder integerHolder = new IntegerHolder(); - - BeanUtils.copyProperties(numberHolder, integerHolder); - assertThat(numberHolder.getNumber()).isEqualTo(42); - assertThat(integerHolder.getNumber()).isNull(); - } - - /** - * {@code List} can NOT be copied to {@code List}. - */ - @Test - void copyPropertiesDoesNotHonorGenericTypeMismatches() { - IntegerListHolder1 integerListHolder = new IntegerListHolder1(); - integerListHolder.getList().add(42); - LongListHolder longListHolder = new LongListHolder(); - - BeanUtils.copyProperties(integerListHolder, longListHolder); - assertThat(integerListHolder.getList()).containsExactly(42); - assertThat(longListHolder.getList()).isEmpty(); - } - - /** - * {@code List} can NOT be copied to {@code List}. - */ - @Test - void copyPropertiesDoesNotHonorGenericTypeMismatchesFromSubTypeToSuperType() { - IntegerListHolder1 integerListHolder = new IntegerListHolder1(); - integerListHolder.getList().add(42); - NumberListHolder numberListHolder = new NumberListHolder(); - - BeanUtils.copyProperties(integerListHolder, numberListHolder); - assertThat(integerListHolder.getList()).containsExactly(42); - assertThat(numberListHolder.getList()).isEmpty(); - } - - @Test // gh-26531 - void copyPropertiesIgnoresGenericsIfSourceOrTargetHasUnresolvableGenerics() throws Exception { - Order original = new Order("test", List.of("foo", "bar")); - - // Create a Proxy that loses the generic type information for the getLineItems() method. - OrderSummary proxy = (OrderSummary) Proxy.newProxyInstance(getClass().getClassLoader(), - new Class[] {OrderSummary.class}, new OrderInvocationHandler(original)); - assertThat(OrderSummary.class.getDeclaredMethod("getLineItems").toGenericString()) - .contains("java.util.List"); - assertThat(proxy.getClass().getDeclaredMethod("getLineItems").toGenericString()) - .contains("java.util.List") - .doesNotContain(""); - - // Ensure that our custom Proxy works as expected. - assertThat(proxy.getId()).isEqualTo("test"); - assertThat(proxy.getLineItems()).containsExactly("foo", "bar"); - - // Copy from proxy to target. - Order target = new Order(); - BeanUtils.copyProperties(proxy, target); - assertThat(target.getId()).isEqualTo("test"); - assertThat(target.getLineItems()).containsExactly("foo", "bar"); - } - - @Test // gh-32888 - public void copyPropertiesWithGenericCglibClass() { - Enhancer enhancer = new Enhancer(); - enhancer.setSuperclass(User.class); - enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> proxy.invokeSuper(obj, args)); - User user = (User) enhancer.create(); - user.setId(1); - user.setName("proxy"); - user.setAddress("addr"); - - User target = new User(); - BeanUtils.copyProperties(user, target); - assertThat(target.getId()).isEqualTo(user.getId()); - assertThat(target.getName()).isEqualTo(user.getName()); - assertThat(target.getAddress()).isEqualTo(user.getAddress()); - } - - @Test - void copyPropertiesWithEditable() throws Exception { - TestBean tb = new TestBean(); - assertThat(tb.getName()).as("Name empty").isNull(); - tb.setAge(32); - tb.setTouchy("bla"); - TestBean tb2 = new TestBean(); - tb2.setName("rod"); - assertThat(tb2.getAge()).as("Age empty").isEqualTo(0); - assertThat(tb2.getTouchy()).as("Touchy empty").isNull(); - - // "touchy" should not be copied: it's not defined in ITestBean - BeanUtils.copyProperties(tb, tb2, ITestBean.class); - assertThat(tb2.getName()).as("Name copied").isNull(); - assertThat(tb2.getAge()).as("Age copied").isEqualTo(32); - assertThat(tb2.getTouchy()).as("Touchy still empty").isNull(); - } - - @Test - void copyPropertiesWithIgnore() throws Exception { - TestBean tb = new TestBean(); - assertThat(tb.getName()).as("Name empty").isNull(); - tb.setAge(32); - tb.setTouchy("bla"); - TestBean tb2 = new TestBean(); - tb2.setName("rod"); - assertThat(tb2.getAge()).as("Age empty").isEqualTo(0); - assertThat(tb2.getTouchy()).as("Touchy empty").isNull(); - - // "spouse", "touchy", "age" should not be copied - BeanUtils.copyProperties(tb, tb2, "spouse", "touchy", "age"); - assertThat(tb2.getName()).as("Name copied").isNull(); - assertThat(tb2.getAge()).as("Age still empty").isEqualTo(0); - assertThat(tb2.getTouchy()).as("Touchy still empty").isNull(); - } - - @Test - void copyPropertiesWithIgnoredNonExistingProperty() { - NameAndSpecialProperty source = new NameAndSpecialProperty(); - source.setName("name"); - TestBean target = new TestBean(); - BeanUtils.copyProperties(source, target, "specialProperty"); - assertThat(target.getName()).isEqualTo("name"); - } - - @Test - void copyPropertiesWithInvalidProperty() { - InvalidProperty source = new InvalidProperty(); - source.setName("name"); - source.setFlag1(true); - source.setFlag2(true); - InvalidProperty target = new InvalidProperty(); - BeanUtils.copyProperties(source, target); - assertThat(target.getName()).isEqualTo("name"); - assertThat((boolean) target.getFlag1()).isTrue(); - assertThat(target.getFlag2()).isTrue(); - } - @Test void resolveSimpleSignature() throws Exception { Method desiredMethod = MethodSignatureBean.class.getMethod("doSomething"); @@ -427,14 +167,14 @@ class BeanUtilsTests { @Test void resolveInvalidSignatureEndParen() { - assertThatIllegalArgumentException().isThrownBy(() -> - BeanUtils.resolveSignature("doSomething(", MethodSignatureBean.class)); + assertThatIllegalArgumentException() + .isThrownBy(() -> BeanUtils.resolveSignature("doSomething(", MethodSignatureBean.class)); } @Test void resolveInvalidSignatureStartParen() { - assertThatIllegalArgumentException().isThrownBy(() -> - BeanUtils.resolveSignature("doSomething)", MethodSignatureBean.class)); + assertThatIllegalArgumentException() + .isThrownBy(() -> BeanUtils.resolveSignature("doSomething)", MethodSignatureBean.class)); } @Test @@ -538,6 +278,272 @@ class BeanUtilsTests { } + @Nested + class CopyPropertiesTests { + + @Test + void copyProperties() throws Exception { + TestBean tb = new TestBean(); + tb.setName("rod"); + tb.setAge(32); + tb.setTouchy("touchy"); + TestBean tb2 = new TestBean(); + assertThat(tb2.getName()).as("Name empty").isNull(); + assertThat(tb2.getAge()).as("Age empty").isEqualTo(0); + assertThat(tb2.getTouchy()).as("Touchy empty").isNull(); + BeanUtils.copyProperties(tb, tb2); + assertThat(tb2.getName()).as("Name copied").isEqualTo(tb.getName()); + assertThat(tb2.getAge()).as("Age copied").isEqualTo(tb.getAge()); + assertThat(tb2.getTouchy()).as("Touchy copied").isEqualTo(tb.getTouchy()); + } + + @Test + void copyPropertiesWithDifferentTypes1() throws Exception { + DerivedTestBean tb = new DerivedTestBean(); + tb.setName("rod"); + tb.setAge(32); + tb.setTouchy("touchy"); + TestBean tb2 = new TestBean(); + assertThat(tb2.getName()).as("Name empty").isNull(); + assertThat(tb2.getAge()).as("Age empty").isEqualTo(0); + assertThat(tb2.getTouchy()).as("Touchy empty").isNull(); + BeanUtils.copyProperties(tb, tb2); + assertThat(tb2.getName()).as("Name copied").isEqualTo(tb.getName()); + assertThat(tb2.getAge()).as("Age copied").isEqualTo(tb.getAge()); + assertThat(tb2.getTouchy()).as("Touchy copied").isEqualTo(tb.getTouchy()); + } + + @Test + void copyPropertiesWithDifferentTypes2() throws Exception { + TestBean tb = new TestBean(); + tb.setName("rod"); + tb.setAge(32); + tb.setTouchy("touchy"); + DerivedTestBean tb2 = new DerivedTestBean(); + assertThat(tb2.getName()).as("Name empty").isNull(); + assertThat(tb2.getAge()).as("Age empty").isEqualTo(0); + assertThat(tb2.getTouchy()).as("Touchy empty").isNull(); + BeanUtils.copyProperties(tb, tb2); + assertThat(tb2.getName()).as("Name copied").isEqualTo(tb.getName()); + assertThat(tb2.getAge()).as("Age copied").isEqualTo(tb.getAge()); + assertThat(tb2.getTouchy()).as("Touchy copied").isEqualTo(tb.getTouchy()); + } + + /** + * {@code Integer} can be copied to {@code Number}. + */ + @Test + void copyPropertiesFromSubTypeToSuperType() { + IntegerHolder integerHolder = new IntegerHolder(); + integerHolder.setNumber(42); + NumberHolder numberHolder = new NumberHolder(); + + BeanUtils.copyProperties(integerHolder, numberHolder); + assertThat(integerHolder.getNumber()).isEqualTo(42); + assertThat(numberHolder.getNumber()).isEqualTo(42); + } + + /** + * {@code List} can be copied to {@code List}. + */ + @Test + void copyPropertiesHonorsGenericTypeMatchesFromIntegerToInteger() { + IntegerListHolder1 integerListHolder1 = new IntegerListHolder1(); + integerListHolder1.getList().add(42); + IntegerListHolder2 integerListHolder2 = new IntegerListHolder2(); + + BeanUtils.copyProperties(integerListHolder1, integerListHolder2); + assertThat(integerListHolder1.getList()).containsExactly(42); + assertThat(integerListHolder2.getList()).containsExactly(42); + } + + /** + * {@code List} can be copied to {@code List}. + */ + @Test + void copyPropertiesHonorsGenericTypeMatchesFromWildcardToWildcard() { + List list = List.of("foo", 42); + WildcardListHolder1 wildcardListHolder1 = new WildcardListHolder1(); + wildcardListHolder1.setList(list); + WildcardListHolder2 wildcardListHolder2 = new WildcardListHolder2(); + assertThat(wildcardListHolder2.getList()).isEmpty(); + + BeanUtils.copyProperties(wildcardListHolder1, wildcardListHolder2); + assertThat(wildcardListHolder1.getList()).isEqualTo(list); + assertThat(wildcardListHolder2.getList()).isEqualTo(list); + } + + /** + * {@code List} can be copied to {@code List}. + */ + @Test + void copyPropertiesHonorsGenericTypeMatchesFromIntegerToWildcard() { + IntegerListHolder1 integerListHolder1 = new IntegerListHolder1(); + integerListHolder1.getList().add(42); + WildcardListHolder2 wildcardListHolder2 = new WildcardListHolder2(); + + BeanUtils.copyProperties(integerListHolder1, wildcardListHolder2); + assertThat(integerListHolder1.getList()).containsExactly(42); + assertThat(wildcardListHolder2.getList()).isEqualTo(List.of(42)); + } + + /** + * {@code List} can be copied to {@code List}. + */ + @Test + void copyPropertiesHonorsGenericTypeMatchesForUpperBoundedWildcard() { + IntegerListHolder1 integerListHolder1 = new IntegerListHolder1(); + integerListHolder1.getList().add(42); + NumberUpperBoundedWildcardListHolder numberListHolder = new NumberUpperBoundedWildcardListHolder(); + + BeanUtils.copyProperties(integerListHolder1, numberListHolder); + assertThat(integerListHolder1.getList()).containsExactly(42); + assertThat(numberListHolder.getList()).isEqualTo(List.of(42)); + } + + /** + * {@code Number} can NOT be copied to {@code Integer}. + */ + @Test + void copyPropertiesDoesNotCopyFromSuperTypeToSubType() { + NumberHolder numberHolder = new NumberHolder(); + numberHolder.setNumber(42); + IntegerHolder integerHolder = new IntegerHolder(); + + BeanUtils.copyProperties(numberHolder, integerHolder); + assertThat(numberHolder.getNumber()).isEqualTo(42); + assertThat(integerHolder.getNumber()).isNull(); + } + + /** + * {@code List} can NOT be copied to {@code List}. + */ + @Test + void copyPropertiesDoesNotHonorGenericTypeMismatches() { + IntegerListHolder1 integerListHolder = new IntegerListHolder1(); + integerListHolder.getList().add(42); + LongListHolder longListHolder = new LongListHolder(); + + BeanUtils.copyProperties(integerListHolder, longListHolder); + assertThat(integerListHolder.getList()).containsExactly(42); + assertThat(longListHolder.getList()).isEmpty(); + } + + /** + * {@code List} can NOT be copied to {@code List}. + */ + @Test + void copyPropertiesDoesNotHonorGenericTypeMismatchesFromSubTypeToSuperType() { + IntegerListHolder1 integerListHolder = new IntegerListHolder1(); + integerListHolder.getList().add(42); + NumberListHolder numberListHolder = new NumberListHolder(); + + BeanUtils.copyProperties(integerListHolder, numberListHolder); + assertThat(integerListHolder.getList()).containsExactly(42); + assertThat(numberListHolder.getList()).isEmpty(); + } + + @Test // gh-26531 + void copyPropertiesIgnoresGenericsIfSourceOrTargetHasUnresolvableGenerics() throws Exception { + Order original = new Order("test", List.of("foo", "bar")); + + // Create a Proxy that loses the generic type information for the getLineItems() method. + OrderSummary proxy = (OrderSummary) Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[] {OrderSummary.class}, new OrderInvocationHandler(original)); + assertThat(OrderSummary.class.getDeclaredMethod("getLineItems").toGenericString()) + .contains("java.util.List"); + assertThat(proxy.getClass().getDeclaredMethod("getLineItems").toGenericString()) + .contains("java.util.List") + .doesNotContain(""); + + // Ensure that our custom Proxy works as expected. + assertThat(proxy.getId()).isEqualTo("test"); + assertThat(proxy.getLineItems()).containsExactly("foo", "bar"); + + // Copy from proxy to target. + Order target = new Order(); + BeanUtils.copyProperties(proxy, target); + assertThat(target.getId()).isEqualTo("test"); + assertThat(target.getLineItems()).containsExactly("foo", "bar"); + } + + @Test // gh-32888 + void copyPropertiesWithGenericCglibClass() { + Enhancer enhancer = new Enhancer(); + enhancer.setSuperclass(User.class); + enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> proxy.invokeSuper(obj, args)); + User user = (User) enhancer.create(); + user.setId(1); + user.setName("proxy"); + user.setAddress("addr"); + + User target = new User(); + BeanUtils.copyProperties(user, target); + assertThat(target.getId()).isEqualTo(user.getId()); + assertThat(target.getName()).isEqualTo(user.getName()); + assertThat(target.getAddress()).isEqualTo(user.getAddress()); + } + + @Test + void copyPropertiesWithEditable() throws Exception { + TestBean tb = new TestBean(); + assertThat(tb.getName()).as("Name empty").isNull(); + tb.setAge(32); + tb.setTouchy("bla"); + TestBean tb2 = new TestBean(); + tb2.setName("rod"); + assertThat(tb2.getAge()).as("Age empty").isEqualTo(0); + assertThat(tb2.getTouchy()).as("Touchy empty").isNull(); + + // "touchy" should not be copied: it's not defined in ITestBean + BeanUtils.copyProperties(tb, tb2, ITestBean.class); + assertThat(tb2.getName()).as("Name copied").isNull(); + assertThat(tb2.getAge()).as("Age copied").isEqualTo(32); + assertThat(tb2.getTouchy()).as("Touchy still empty").isNull(); + } + + @Test + void copyPropertiesWithIgnore() throws Exception { + TestBean tb = new TestBean(); + assertThat(tb.getName()).as("Name empty").isNull(); + tb.setAge(32); + tb.setTouchy("bla"); + TestBean tb2 = new TestBean(); + tb2.setName("rod"); + assertThat(tb2.getAge()).as("Age empty").isEqualTo(0); + assertThat(tb2.getTouchy()).as("Touchy empty").isNull(); + + // "spouse", "touchy", "age" should not be copied + BeanUtils.copyProperties(tb, tb2, "spouse", "touchy", "age"); + assertThat(tb2.getName()).as("Name copied").isNull(); + assertThat(tb2.getAge()).as("Age still empty").isEqualTo(0); + assertThat(tb2.getTouchy()).as("Touchy still empty").isNull(); + } + + @Test + void copyPropertiesWithIgnoredNonExistingProperty() { + NameAndSpecialProperty source = new NameAndSpecialProperty(); + source.setName("name"); + TestBean target = new TestBean(); + BeanUtils.copyProperties(source, target, "specialProperty"); + assertThat(target.getName()).isEqualTo("name"); + } + + @Test + void copyPropertiesWithInvalidProperty() { + InvalidProperty source = new InvalidProperty(); + source.setName("name"); + source.setFlag1(true); + source.setFlag2(true); + InvalidProperty target = new InvalidProperty(); + BeanUtils.copyProperties(source, target); + assertThat(target.getName()).isEqualTo("name"); + assertThat((boolean) target.getFlag1()).isTrue(); + assertThat(target.getFlag2()).isTrue(); + } + } + + public record RecordWithMultiplePublicConstructors(String value, String name) { @SuppressWarnings("unused") public RecordWithMultiplePublicConstructors(String value) {