diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoPostProcessor.java b/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoPostProcessor.java index 93b92095f2e..a46821d9b91 100644 --- a/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoPostProcessor.java +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoPostProcessor.java @@ -18,14 +18,18 @@ package org.springframework.boot.test.mock.mockito; import java.beans.PropertyDescriptor; import java.lang.reflect.Field; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; +import org.springframework.aop.scope.ScopedProxyUtils; import org.springframework.aop.support.AopUtils; import org.springframework.beans.BeansException; import org.springframework.beans.PropertyValues; @@ -208,8 +212,8 @@ public class MockitoPostProcessor extends InstantiationAwareBeanPostProcessorAda if (StringUtils.hasLength(mockDefinition.getName())) { return mockDefinition.getName(); } - String[] existingBeans = beanFactory - .getBeanNamesForType(mockDefinition.getClassToMock()); + String[] existingBeans = getExistingBeans(beanFactory, + mockDefinition.getClassToMock()); if (ObjectUtils.isEmpty(existingBeans)) { return this.beanNameGenerator.generateBeanName(beanDefinition, registry); } @@ -224,8 +228,8 @@ public class MockitoPostProcessor extends InstantiationAwareBeanPostProcessorAda private void registerSpy(ConfigurableListableBeanFactory beanFactory, BeanDefinitionRegistry registry, SpyDefinition definition, Field field) { - String[] existingBeans = beanFactory - .getBeanNamesForType(definition.getClassToSpy()); + String[] existingBeans = getExistingBeans(beanFactory, + definition.getClassToSpy()); if (ObjectUtils.isEmpty(existingBeans)) { createSpy(registry, definition, field); } @@ -234,6 +238,27 @@ public class MockitoPostProcessor extends InstantiationAwareBeanPostProcessorAda } } + private String[] getExistingBeans(ConfigurableListableBeanFactory beanFactory, + Class type) { + List beans = new ArrayList( + Arrays.asList(beanFactory.getBeanNamesForType(type))); + for (Iterator iterator = beans.iterator(); iterator.hasNext();) { + if (isScopedTarget(iterator.next())) { + iterator.remove(); + } + } + return beans.toArray(new String[beans.size()]); + } + + private boolean isScopedTarget(String beanName) { + try { + return ScopedProxyUtils.isScopedTarget(beanName); + } + catch (Throwable ex) { + return false; + } + } + private void createSpy(BeanDefinitionRegistry registry, SpyDefinition definition, Field field) { RootBeanDefinition beanDefinition = new RootBeanDefinition( @@ -309,7 +334,7 @@ public class MockitoPostProcessor extends InstantiationAwareBeanPostProcessorAda Assert.state(ReflectionUtils.getField(field, target) == null, "The field " + field + " cannot have an existing value"); Object bean = this.beanFactory.getBean(beanName, field.getType()); - if (definition.isProxyTargetAware() && AopUtils.isAopProxy(bean)) { + if (definition.isProxyTargetAware() && isAopProxy(bean)) { MockitoAopProxyTargetInterceptor.applyTo(bean); } ReflectionUtils.setField(field, target, bean); @@ -319,6 +344,15 @@ public class MockitoPostProcessor extends InstantiationAwareBeanPostProcessorAda } } + private boolean isAopProxy(Object object) { + try { + return AopUtils.isAopProxy(object); + } + catch (Throwable ex) { + return false; + } + } + @Override public int getOrder() { return Ordered.LOWEST_PRECEDENCE - 10; diff --git a/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnScopedProxyTests.java b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnScopedProxyTests.java new file mode 100644 index 00000000000..9aba76918ca --- /dev/null +++ b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnScopedProxyTests.java @@ -0,0 +1,69 @@ +/* + * Copyright 2012-2016 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 + * + * http://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.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.example.ExampleService; +import org.springframework.boot.test.mock.mockito.example.ExampleServiceCaller; +import org.springframework.boot.test.mock.mockito.example.FailingExampleService; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Scope; +import org.springframework.context.annotation.ScopedProxyMode; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; + +/** + * Test {@link MockBean} when used in combination with scoped proxy targets. + * + * @author Phillip Webb + * @see gh-5724 + */ +@RunWith(SpringRunner.class) +public class MockBeanOnScopedProxyTests { + + @MockBean + private ExampleService exampleService; + + @Autowired + private ExampleServiceCaller caller; + + @Test + public void testMocking() throws Exception { + given(this.caller.getService().greeting()).willReturn("Boot"); + assertThat(this.caller.sayGreeting()).isEqualTo("I say Boot"); + } + + @Configuration + @Import({ ExampleServiceCaller.class }) + static class Config { + + @Bean + @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS) + public ExampleService exampleService() { + return new FailingExampleService(); + } + + } + +}