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 94337fe101..6ff5666169 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 @@ -37,6 +37,8 @@ import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.BeanPostProcessor; @@ -250,7 +252,7 @@ public class MockitoPostProcessor extends InstantiationAwareBeanPostProcessorAda createSpy(registry, definition, field); } else { - registerSpies(definition, field, existingBeans); + registerSpies(registry, definition, field, existingBeans); } } @@ -307,15 +309,41 @@ public class MockitoPostProcessor extends InstantiationAwareBeanPostProcessorAda registerSpy(definition, field, beanName); } - private void registerSpies(SpyDefinition definition, Field field, - String[] existingBeans) { - Assert.state(field == null || existingBeans.length == 1, - "Unable to register spy bean " + definition.getTypeToSpy() - + " expected a single existing bean to replace but found " - + new TreeSet(Arrays.asList(existingBeans))); - for (String beanName : existingBeans) { - registerSpy(definition, field, beanName); + private void registerSpies(BeanDefinitionRegistry registry, SpyDefinition definition, + Field field, String[] existingBeans) { + ResolvableType type = definition.getTypeToSpy(); + try { + if (ObjectUtils.isEmpty(existingBeans)) { + throw new NoSuchBeanDefinitionException(type); + } + if (existingBeans.length > 1) { + existingBeans = new String[] { + determinePrimaryCandidate(registry, existingBeans, type) }; + } + registerSpy(definition, field, existingBeans[0]); } + catch (RuntimeException ex) { + throw new IllegalStateException( + "Unable to register spy bean " + definition.getTypeToSpy(), ex); + } + } + + private String determinePrimaryCandidate(BeanDefinitionRegistry registry, + String[] candidateBeanNames, ResolvableType type) { + String primaryBeanName = null; + for (String candidateBeanName : candidateBeanNames) { + BeanDefinition beanDefinition = registry.getBeanDefinition(candidateBeanName); + if (beanDefinition.isPrimary()) { + if (primaryBeanName != null) { + throw new NoUniqueBeanDefinitionException(type.resolve(), + candidateBeanNames.length, + "more than one 'primary' bean found among candidates: " + + Arrays.asList(candidateBeanNames)); + } + primaryBeanName = candidateBeanName; + } + } + return primaryBeanName; } private void registerSpy(SpyDefinition definition, Field field, String beanName) { diff --git a/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForMultipleExistingBeansIntegrationTests.java b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForMultipleExistingBeansIntegrationTests.java new file mode 100644 index 0000000000..c4880c533d --- /dev/null +++ b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForMultipleExistingBeansIntegrationTests.java @@ -0,0 +1,73 @@ +/* + * 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.mockito.internal.util.MockUtil; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.example.ExampleGenericStringServiceCaller; +import org.springframework.boot.test.mock.mockito.example.SimpleExampleStringGenericService; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Primary; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verify; + +/** + * Test {@link SpyBean} on a test class field can be used to inject new spy instances. + * + * @author Phillip Webb + */ +@RunWith(SpringRunner.class) +public class SpyBeanOnTestFieldForMultipleExistingBeansIntegrationTests { + + @SpyBean + private SimpleExampleStringGenericService spy; + + @Autowired + private ExampleGenericStringServiceCaller caller; + + @Test + public void testSpying() throws Exception { + assertThat(this.caller.sayGreeting()).isEqualTo("I say two"); + assertThat(new MockUtil().getMockName(this.spy).toString()).isEqualTo("two"); + verify(this.spy).greeting(); + } + + @Configuration + @Import(ExampleGenericStringServiceCaller.class) + static class Config { + + @Bean + public SimpleExampleStringGenericService one() { + return new SimpleExampleStringGenericService("one"); + } + + @Bean + @Primary + public SimpleExampleStringGenericService two() { + return new SimpleExampleStringGenericService("two"); + } + + } + +} diff --git a/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/ExampleGenericStringServiceCaller.java b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/ExampleGenericStringServiceCaller.java new file mode 100644 index 0000000000..08df288575 --- /dev/null +++ b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/ExampleGenericStringServiceCaller.java @@ -0,0 +1,41 @@ +/* + * 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.example; + +/** + * Example bean for mocking tests that calls {@link ExampleGenericService}. + * + * @author Phillip Webb + */ +public class ExampleGenericStringServiceCaller { + + private final ExampleGenericService stringService; + + public ExampleGenericStringServiceCaller( + ExampleGenericService stringService) { + this.stringService = stringService; + } + + public ExampleGenericService getStringService() { + return this.stringService; + } + + public String sayGreeting() { + return "I say " + this.stringService.greeting(); + } + +} diff --git a/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/SimpleExampleStringGenericService.java b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/SimpleExampleStringGenericService.java index fa7a6b5840..ce2e721c60 100644 --- a/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/SimpleExampleStringGenericService.java +++ b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/SimpleExampleStringGenericService.java @@ -23,9 +23,19 @@ package org.springframework.boot.test.mock.mockito.example; */ public class SimpleExampleStringGenericService implements ExampleGenericService { + private final String greeting; + + public SimpleExampleStringGenericService() { + this("simple"); + } + + public SimpleExampleStringGenericService(String greeting) { + this.greeting = greeting; + } + @Override public String greeting() { - return "simple"; + return this.greeting; } }