From 486ceecc9a10b603e1ae7d42e34aec3efa39d1bb Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 21 May 2024 15:27:59 +0100 Subject: [PATCH] Use the bean factory to get the type produced by a factory bean Previously, we only looked at the OBJECT_TYPE_ATTRIBUTE on the factory bean's definition. This did not work for situations where the information's provided by the definition's target type rather than the attribute. Rather than manually considering the target type in addition to the existing consideration of the attribute, we now ask the bean factory for the type that will be produced by the factory bean instead. This should insulate us from any changes and enhancements in Framework in the future. Fixes gh-40234 --- .../mock/mockito/MockitoPostProcessor.java | 5 +- ...ProducedByFactoryBeanIntegrationTests.java | 93 +++++++++++++++++++ 2 files changed, 95 insertions(+), 3 deletions(-) create mode 100644 spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForExistingGenericBeanProducedByFactoryBeanIntegrationTests.java diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoPostProcessor.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoPostProcessor.java index 334d90dcfb1..6b36e7f0c98 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoPostProcessor.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoPostProcessor.java @@ -254,9 +254,8 @@ public class MockitoPostProcessor implements InstantiationAwareBeanPostProcessor Class type = resolvableType.resolve(Object.class); for (String beanName : beanFactory.getBeanNamesForType(FactoryBean.class, true, false)) { beanName = BeanFactoryUtils.transformedBeanName(beanName); - BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName); - Object attribute = beanDefinition.getAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE); - if (resolvableType.equals(attribute) || type.equals(attribute)) { + Class producedType = beanFactory.getType(beanName, false); + if (type.equals(producedType)) { beans.add(beanName); } } diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForExistingGenericBeanProducedByFactoryBeanIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForExistingGenericBeanProducedByFactoryBeanIntegrationTests.java new file mode 100644 index 00000000000..b84a8f1dadd --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForExistingGenericBeanProducedByFactoryBeanIntegrationTests.java @@ -0,0 +1,93 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.mock.mockito; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.boot.test.mock.mockito.example.ExampleGenericService; +import org.springframework.boot.test.mock.mockito.example.SimpleExampleStringGenericService; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.ResolvableType; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test {@link SpyBean @SpyBean} on a test class field can be used to replace an existing + * bean with generics that's produced by a factory bean. + * + * @author Andy Wilkinson + */ +@ExtendWith(SpringExtension.class) +class SpyBeanOnTestFieldForExistingGenericBeanProducedByFactoryBeanIntegrationTests { + + // gh-40234 + + @SpyBean(name = "exampleService") + private ExampleGenericService exampleService; + + @Test + void testSpying() { + assertThat(Mockito.mockingDetails(this.exampleService).isSpy()).isTrue(); + assertThat(Mockito.mockingDetails(this.exampleService).getMockCreationSettings().getSpiedInstance()) + .isInstanceOf(SimpleExampleStringGenericService.class); + } + + @Configuration(proxyBeanMethods = false) + @Import(FactoryBeanRegistrar.class) + static class SpyBeanOnTestFieldForExistingBeanConfig { + + } + + static class FactoryBeanRegistrar implements ImportBeanDefinitionRegistrar { + + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, + BeanDefinitionRegistry registry) { + RootBeanDefinition definition = new RootBeanDefinition(ExampleGenericServiceFactoryBean.class); + definition.setTargetType(ResolvableType.forClassWithGenerics(ExampleGenericServiceFactoryBean.class, null, + ExampleGenericService.class)); + registry.registerBeanDefinition("exampleService", definition); + } + + } + + static class ExampleGenericServiceFactoryBean> implements FactoryBean { + + @SuppressWarnings("unchecked") + @Override + public U getObject() throws Exception { + return (U) new SimpleExampleStringGenericService(); + } + + @Override + @SuppressWarnings("rawtypes") + public Class getObjectType() { + return ExampleGenericService.class; + } + + } + +}