diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/Definition.java b/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/Definition.java index f8a0b175e3..1a6c641de7 100644 --- a/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/Definition.java +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/Definition.java @@ -16,8 +16,6 @@ package org.springframework.boot.test.mock.mockito; -import java.lang.reflect.AnnotatedElement; - import org.springframework.util.ObjectUtils; /** @@ -30,28 +28,20 @@ abstract class Definition { private static final int MULTIPLIER = 31; - private final AnnotatedElement element; - private final String name; private final MockReset reset; private final boolean proxyTargetAware; - Definition(AnnotatedElement element, String name, MockReset reset, - boolean proxyTargetAware) { - this.element = element; + private final QualifierDefinition qualifier; + + Definition(String name, MockReset reset, boolean proxyTargetAware, + QualifierDefinition qualifier) { this.name = name; this.reset = (reset != null ? reset : MockReset.AFTER); this.proxyTargetAware = proxyTargetAware; - } - - /** - * Return the {@link AnnotatedElement} that holds this definition. - * @return the element that defines this definition or {@code null} - */ - public AnnotatedElement getElement() { - return this.element; + this.qualifier = qualifier; } /** @@ -78,6 +68,14 @@ abstract class Definition { return this.proxyTargetAware; } + /** + * Return the qualifier or {@code null}. + * @return the qualifier + */ + public QualifierDefinition getQualifier() { + return this.qualifier; + } + @Override public int hashCode() { int result = 1; @@ -85,6 +83,7 @@ abstract class Definition { result = MULTIPLIER * result + ObjectUtils.nullSafeHashCode(this.reset); result = MULTIPLIER * result + ObjectUtils.nullSafeHashCode(this.proxyTargetAware); + result = MULTIPLIER * result + ObjectUtils.nullSafeHashCode(this.qualifier); return result; } @@ -102,6 +101,7 @@ abstract class Definition { result &= ObjectUtils.nullSafeEquals(this.reset, other.reset); result &= ObjectUtils.nullSafeEquals(this.proxyTargetAware, other.proxyTargetAware); + result &= ObjectUtils.nullSafeEquals(this.qualifier, other.qualifier); return result; } diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/DefinitionsParser.java b/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/DefinitionsParser.java index faeddf0d82..d95e7fde27 100644 --- a/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/DefinitionsParser.java +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/DefinitionsParser.java @@ -91,10 +91,11 @@ class DefinitionsParser { "The name attribute can only be used when mocking a single class"); } for (ResolvableType typeToMock : typesToMock) { - MockDefinition definition = new MockDefinition(element, annotation.name(), - typeToMock, annotation.extraInterfaces(), annotation.answer(), - annotation.serializable(), annotation.reset()); - addDefinition(definition, "mock"); + MockDefinition definition = new MockDefinition(annotation.name(), typeToMock, + annotation.extraInterfaces(), annotation.answer(), + annotation.serializable(), annotation.reset(), + QualifierDefinition.forElement(element)); + addDefinition(element, definition, "mock"); } } @@ -107,16 +108,17 @@ class DefinitionsParser { "The name attribute can only be used when spying a single class"); } for (ResolvableType typeToSpy : typesToSpy) { - SpyDefinition definition = new SpyDefinition(element, annotation.name(), - typeToSpy, annotation.reset(), annotation.proxyTargetAware()); - addDefinition(definition, "spy"); + SpyDefinition definition = new SpyDefinition(annotation.name(), typeToSpy, + annotation.reset(), annotation.proxyTargetAware(), + QualifierDefinition.forElement(element)); + addDefinition(element, definition, "spy"); } } - private void addDefinition(Definition definition, String type) { + private void addDefinition(AnnotatedElement element, Definition definition, + String type) { boolean isNewDefinition = this.definitions.add(definition); Assert.state(isNewDefinition, "Duplicate " + type + " definition " + definition); - AnnotatedElement element = definition.getElement(); if (element instanceof Field) { Field field = (Field) element; this.definitionFields.put(definition, field); diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockDefinition.java b/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockDefinition.java index 2284741b58..07df1db3fa 100644 --- a/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockDefinition.java +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockDefinition.java @@ -16,7 +16,6 @@ package org.springframework.boot.test.mock.mockito; -import java.lang.reflect.AnnotatedElement; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashSet; @@ -50,10 +49,10 @@ class MockDefinition extends Definition { private final boolean serializable; - MockDefinition(AnnotatedElement element, String name, ResolvableType typeToMock, - Class[] extraInterfaces, Answers answer, boolean serializable, - MockReset reset) { - super(element, name, reset, false); + MockDefinition(String name, ResolvableType typeToMock, Class[] extraInterfaces, + Answers answer, boolean serializable, MockReset reset, + QualifierDefinition qualifier) { + super(name, reset, false, qualifier); Assert.notNull(typeToMock, "TypeToMock must not be null"); this.typeToMock = typeToMock; this.extraInterfaces = asClassSet(extraInterfaces); 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 a816896f92..90145d27bb 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 @@ -17,7 +17,6 @@ package org.springframework.boot.test.mock.mockito; import java.beans.PropertyDescriptor; -import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Field; import java.util.Arrays; import java.util.HashMap; @@ -44,7 +43,6 @@ import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.ConstructorArgumentValues; import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder; -import org.springframework.beans.factory.config.DependencyDescriptor; import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.support.BeanDefinitionRegistry; @@ -209,9 +207,8 @@ public class MockitoPostProcessor extends InstantiationAwareBeanPostProcessorAda definition.setFactoryMethodName("createMock"); definition.getConstructorArgumentValues().addIndexedArgumentValue(0, mockDefinition); - AnnotatedElement element = mockDefinition.getElement(); - if (element instanceof Field) { - definition.setQualifiedElement(element); + if (mockDefinition.getQualifier() != null) { + mockDefinition.getQualifier().applyTo(definition); } return definition; } @@ -232,17 +229,17 @@ public class MockitoPostProcessor extends InstantiationAwareBeanPostProcessorAda if (StringUtils.hasLength(mockDefinition.getName())) { return mockDefinition.getName(); } - String[] existingBeans = findCandidateBeans(beanFactory, mockDefinition); - if (ObjectUtils.isEmpty(existingBeans)) { + Set existingBeans = findCandidateBeans(beanFactory, mockDefinition); + if (existingBeans.isEmpty()) { return this.beanNameGenerator.generateBeanName(beanDefinition, registry); } - if (existingBeans.length == 1) { - return existingBeans[0]; + if (existingBeans.size() == 1) { + return existingBeans.iterator().next(); } throw new IllegalStateException( "Unable to register mock bean " + mockDefinition.getTypeToMock() + " expected a single matching bean to replace but found " - + new TreeSet(Arrays.asList(existingBeans))); + + existingBeans); } private void registerSpy(ConfigurableListableBeanFactory beanFactory, @@ -256,22 +253,17 @@ public class MockitoPostProcessor extends InstantiationAwareBeanPostProcessorAda } } - private String[] findCandidateBeans(ConfigurableListableBeanFactory beanFactory, + private Set findCandidateBeans(ConfigurableListableBeanFactory beanFactory, MockDefinition mockDefinition) { - String[] beans = getExistingBeans(beanFactory, mockDefinition.getTypeToMock()); - // Attempt to filter using qualifiers - if (beans.length > 1 && mockDefinition.getElement() instanceof Field) { - DependencyDescriptor descriptor = new DependencyDescriptor( - (Field) mockDefinition.getElement(), true); - Set candidates = new LinkedHashSet(); - for (String bean : beans) { - if (beanFactory.isAutowireCandidate(bean, descriptor)) { - candidates.add(bean); - } + QualifierDefinition qualifier = mockDefinition.getQualifier(); + Set candidates = new TreeSet(); + for (String candidate : getExistingBeans(beanFactory, + mockDefinition.getTypeToMock())) { + if (qualifier == null || qualifier.matches(beanFactory, candidate)) { + candidates.add(candidate); } - return candidates.toArray(new String[candidates.size()]); } - return beans; + return candidates; } private String[] getExistingBeans(ConfigurableListableBeanFactory beanFactory, diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/QualifierDefinition.java b/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/QualifierDefinition.java new file mode 100644 index 0000000000..c66713b468 --- /dev/null +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/QualifierDefinition.java @@ -0,0 +1,110 @@ +/* + * 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 java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Field; +import java.util.HashSet; +import java.util.Set; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.config.DependencyDescriptor; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.core.annotation.AnnotationUtils; + +/** + * Definition of a Spring {@link Qualifier @Qualifier}. + * + * @author Phillip Webb + * @author Stephane Nicoll + * @see Definition + */ +class QualifierDefinition { + + private final Field field; + + private final DependencyDescriptor descriptor; + + private final Set annotations; + + QualifierDefinition(Field field, Set annotations) { + // We can't use the field or descriptor as part of the context key + // but we can assume that if two fields have the same qualifiers then + // it's safe for Spring to use either for qualifier logic + this.field = field; + this.descriptor = new DependencyDescriptor(field, true); + this.annotations = annotations; + } + + public boolean matches(ConfigurableListableBeanFactory beanFactory, String beanName) { + return beanFactory.isAutowireCandidate(beanName, this.descriptor); + } + + public void applyTo(RootBeanDefinition definition) { + definition.setQualifiedElement(this.field); + } + + @Override + public int hashCode() { + return this.annotations.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj == null || !getClass().isAssignableFrom(obj.getClass())) { + return false; + } + QualifierDefinition other = (QualifierDefinition) obj; + return this.annotations.equals(other.annotations); + } + + public static QualifierDefinition forElement(AnnotatedElement element) { + if (element != null && element instanceof Field) { + Field field = (Field) element; + Set annotations = getQualifierAnnotations(field); + if (!annotations.isEmpty()) { + return new QualifierDefinition(field, annotations); + } + } + return null; + } + + private static Set getQualifierAnnotations(Field field) { + // Assume that any annotations other than @MockBean/@SpyBean are qualifiers + Annotation[] candidates = field.getDeclaredAnnotations(); + Set annotations = new HashSet(candidates.length); + for (Annotation candidate : candidates) { + if (!isMockOrSpyAnnotation(candidate)) { + annotations.add(candidate); + } + } + return annotations; + } + + private static boolean isMockOrSpyAnnotation(Annotation candidate) { + Class type = candidate.annotationType(); + return (type.equals(MockBean.class) || type.equals(SpyBean.class) + || AnnotationUtils.isAnnotationMetaPresent(type, MockBean.class) + || AnnotationUtils.isAnnotationMetaPresent(type, SpyBean.class)); + } + +} diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/SpyDefinition.java b/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/SpyDefinition.java index 3c88a45b4d..252dd8ce6b 100644 --- a/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/SpyDefinition.java +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/SpyDefinition.java @@ -16,8 +16,6 @@ package org.springframework.boot.test.mock.mockito; -import java.lang.reflect.AnnotatedElement; - import org.mockito.MockSettings; import org.mockito.Mockito; import org.mockito.internal.util.MockUtil; @@ -41,9 +39,9 @@ class SpyDefinition extends Definition { private final ResolvableType typeToSpy; - SpyDefinition(AnnotatedElement element, String name, ResolvableType typeToSpy, - MockReset reset, boolean proxyTargetAware) { - super(element, name, reset, proxyTargetAware); + SpyDefinition(String name, ResolvableType typeToSpy, MockReset reset, + boolean proxyTargetAware, QualifierDefinition qualifier) { + super(name, reset, proxyTargetAware, qualifier); Assert.notNull(typeToSpy, "TypeToSpy must not be null"); this.typeToSpy = typeToSpy; diff --git a/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/DefinitionsParserTests.java b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/DefinitionsParserTests.java index 6b590e995c..b4aa87be24 100644 --- a/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/DefinitionsParserTests.java +++ b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/DefinitionsParserTests.java @@ -24,6 +24,7 @@ import org.junit.Test; import org.junit.rules.ExpectedException; import org.mockito.Answers; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.test.mock.mockito.example.ExampleExtraInterface; import org.springframework.boot.test.mock.mockito.example.ExampleService; import org.springframework.boot.test.mock.mockito.example.ExampleServiceCaller; @@ -67,7 +68,6 @@ public class DefinitionsParserTests { this.parser.parse(MockBeanAttributes.class); assertThat(getDefinitions()).hasSize(1); MockDefinition definition = getMockDefinition(0); - assertThat(definition.getElement()).isEqualTo(MockBeanAttributes.class); assertThat(definition.getName()).isEqualTo("Name"); assertThat(definition.getTypeToMock().resolve()).isEqualTo(ExampleService.class); assertThat(definition.getExtraInterfaces()) @@ -75,6 +75,7 @@ public class DefinitionsParserTests { assertThat(definition.getAnswer()).isEqualTo(Answers.RETURNS_SMART_NULLS); assertThat(definition.isSerializable()).isEqualTo(true); assertThat(definition.getReset()).isEqualTo(MockReset.NONE); + assertThat(definition.getQualifier()).isNull(); } @Test @@ -82,14 +83,15 @@ public class DefinitionsParserTests { this.parser.parse(MockBeanOnClassAndField.class); assertThat(getDefinitions()).hasSize(2); MockDefinition classDefinition = getMockDefinition(0); - assertThat(classDefinition.getElement()).isEqualTo(MockBeanOnClassAndField.class); assertThat(classDefinition.getTypeToMock().resolve()) .isEqualTo(ExampleService.class); + assertThat(classDefinition.getQualifier()).isNull(); MockDefinition fieldDefinition = getMockDefinition(1); - assertThat(fieldDefinition.getElement()).isEqualTo( - ReflectionUtils.findField(MockBeanOnClassAndField.class, "caller")); assertThat(fieldDefinition.getTypeToMock().resolve()) .isEqualTo(ExampleServiceCaller.class); + QualifierDefinition qualifier = QualifierDefinition.forElement( + ReflectionUtils.findField(MockBeanOnClassAndField.class, "caller")); + assertThat(fieldDefinition.getQualifier()).isNotNull().isEqualTo(qualifier); } @Test @@ -148,11 +150,11 @@ public class DefinitionsParserTests { this.parser.parse(SpyBeanAttributes.class); assertThat(getDefinitions()).hasSize(1); SpyDefinition definition = getSpyDefinition(0); - assertThat(definition.getElement()).isEqualTo(SpyBeanAttributes.class); assertThat(definition.getName()).isEqualTo("Name"); assertThat(definition.getTypeToSpy().resolve()) .isEqualTo(RealExampleService.class); assertThat(definition.getReset()).isEqualTo(MockReset.NONE); + assertThat(definition.getQualifier()).isNull(); } @Test @@ -160,12 +162,13 @@ public class DefinitionsParserTests { this.parser.parse(SpyBeanOnClassAndField.class); assertThat(getDefinitions()).hasSize(2); SpyDefinition classDefinition = getSpyDefinition(0); - assertThat(classDefinition.getElement()).isEqualTo(SpyBeanOnClassAndField.class); + assertThat(classDefinition.getQualifier()).isNull(); assertThat(classDefinition.getTypeToSpy().resolve()) .isEqualTo(RealExampleService.class); SpyDefinition fieldDefinition = getSpyDefinition(1); - assertThat(fieldDefinition.getElement()).isEqualTo( + QualifierDefinition qualifier = QualifierDefinition.forElement( ReflectionUtils.findField(SpyBeanOnClassAndField.class, "caller")); + assertThat(fieldDefinition.getQualifier()).isNotNull().isEqualTo(qualifier); } @Test @@ -232,6 +235,7 @@ public class DefinitionsParserTests { static class MockBeanOnClassAndField { @MockBean(ExampleServiceCaller.class) + @Qualifier("test") private Object caller; } @@ -279,6 +283,7 @@ public class DefinitionsParserTests { static class SpyBeanOnClassAndField { @SpyBean(ExampleServiceCaller.class) + @Qualifier("test") private Object caller; } diff --git a/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockDefinitionTests.java b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockDefinitionTests.java index 11cc007e49..4f4dcbda24 100644 --- a/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockDefinitionTests.java +++ b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockDefinitionTests.java @@ -28,6 +28,7 @@ import org.springframework.boot.test.mock.mockito.example.ExampleService; import org.springframework.core.ResolvableType; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; /** * Tests for {@link MockDefinition}. @@ -46,28 +47,28 @@ public class MockDefinitionTests { public void classToMockMustNotBeNull() throws Exception { this.thrown.expect(IllegalArgumentException.class); this.thrown.expectMessage("TypeToMock must not be null"); - new MockDefinition(null, null, null, null, null, false, null); + new MockDefinition(null, null, null, null, false, null, null); } @Test public void createWithDefaults() throws Exception { - MockDefinition definition = new MockDefinition(null, null, EXAMPLE_SERVICE_TYPE, - null, null, false, null); - assertThat(definition.getElement()).isNull(); + MockDefinition definition = new MockDefinition(null, EXAMPLE_SERVICE_TYPE, null, + null, false, null, null); assertThat(definition.getName()).isNull(); assertThat(definition.getTypeToMock()).isEqualTo(EXAMPLE_SERVICE_TYPE); assertThat(definition.getExtraInterfaces()).isEmpty(); assertThat(definition.getAnswer()).isEqualTo(Answers.RETURNS_DEFAULTS); assertThat(definition.isSerializable()).isFalse(); assertThat(definition.getReset()).isEqualTo(MockReset.AFTER); + assertThat(definition.getQualifier()).isNull(); } @Test public void createExplicit() throws Exception { - MockDefinition definition = new MockDefinition(getClass(), "name", - EXAMPLE_SERVICE_TYPE, new Class[] { ExampleExtraInterface.class }, - Answers.RETURNS_SMART_NULLS, true, MockReset.BEFORE); - assertThat(definition.getElement()).isEqualTo(getClass()); + QualifierDefinition qualifier = mock(QualifierDefinition.class); + MockDefinition definition = new MockDefinition("name", EXAMPLE_SERVICE_TYPE, + new Class[] { ExampleExtraInterface.class }, + Answers.RETURNS_SMART_NULLS, true, MockReset.BEFORE, qualifier); assertThat(definition.getName()).isEqualTo("name"); assertThat(definition.getTypeToMock()).isEqualTo(EXAMPLE_SERVICE_TYPE); assertThat(definition.getExtraInterfaces()) @@ -76,13 +77,14 @@ public class MockDefinitionTests { assertThat(definition.isSerializable()).isTrue(); assertThat(definition.getReset()).isEqualTo(MockReset.BEFORE); assertThat(definition.isProxyTargetAware()).isFalse(); + assertThat(definition.getQualifier()).isEqualTo(qualifier); } @Test public void createMock() throws Exception { - MockDefinition definition = new MockDefinition(null, "name", EXAMPLE_SERVICE_TYPE, + MockDefinition definition = new MockDefinition("name", EXAMPLE_SERVICE_TYPE, new Class[] { ExampleExtraInterface.class }, - Answers.RETURNS_SMART_NULLS, true, MockReset.BEFORE); + Answers.RETURNS_SMART_NULLS, true, MockReset.BEFORE, null); ExampleService mock = definition.createMock(); MockCreationSettings settings = new MockUtil().getMockSettings(mock); assertThat(mock).isInstanceOf(ExampleService.class); diff --git a/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoContextCustomizerTests.java b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoContextCustomizerTests.java index ffc90ee871..aa25164707 100644 --- a/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoContextCustomizerTests.java +++ b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoContextCustomizerTests.java @@ -53,8 +53,8 @@ public class MockitoContextCustomizerTests { } private MockDefinition createTestMockDefinition(Class typeToMock) { - return new MockDefinition(null, null, ResolvableType.forClass(typeToMock), null, - null, false, null); + return new MockDefinition(null, ResolvableType.forClass(typeToMock), null, null, + false, null, null); } } diff --git a/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/QualifierDefinitionTests.java b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/QualifierDefinitionTests.java new file mode 100644 index 0000000000..b636fc42f0 --- /dev/null +++ b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/QualifierDefinitionTests.java @@ -0,0 +1,178 @@ +/* + * 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 java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Field; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.config.DependencyDescriptor; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.ReflectionUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.verify; + +/** + * Tests for {@link QualifierDefinition}. + * + * @author Phillip Webb + */ +public class QualifierDefinitionTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Mock + private ConfigurableListableBeanFactory beanFactory; + + @Captor + private ArgumentCaptor descriptorCaptor; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void forElementFieldIsNullShouldReturnNull() throws Exception { + assertThat(QualifierDefinition.forElement((Field) null)).isNull(); + } + + @Test + public void forElementWhenElementIsNotFieldShouldReturnNull() throws Exception { + assertThat(QualifierDefinition.forElement(getClass())).isNull(); + } + + @Test + public void forElementWhenElementIsFieldWithNoQualifiersShouldReturnNull() + throws Exception { + QualifierDefinition definition = QualifierDefinition + .forElement(ReflectionUtils.findField(ConfigA.class, "noQualifier")); + assertThat(definition).isNull(); + } + + @Test + public void forElementWhenElementIsFieldWithQualifierShouldReturnDefinition() + throws Exception { + QualifierDefinition definition = QualifierDefinition + .forElement(ReflectionUtils.findField(ConfigA.class, "directQualifier")); + assertThat(definition).isNotNull(); + } + + @Test + public void matchesShouldCallBeanFactory() throws Exception { + Field field = ReflectionUtils.findField(ConfigA.class, "directQualifier"); + QualifierDefinition qualifierDefinition = QualifierDefinition.forElement(field); + qualifierDefinition.matches(this.beanFactory, "bean"); + verify(this.beanFactory).isAutowireCandidate(eq("bean"), + this.descriptorCaptor.capture()); + assertThat(this.descriptorCaptor.getValue().getAnnotatedElement()) + .isEqualTo(field); + } + + @Test + public void applyToShouldSetQualifierElement() throws Exception { + Field field = ReflectionUtils.findField(ConfigA.class, "directQualifier"); + QualifierDefinition qualifierDefinition = QualifierDefinition.forElement(field); + RootBeanDefinition definition = new RootBeanDefinition(); + qualifierDefinition.applyTo(definition); + assertThat(definition.getQualifiedElement()).isEqualTo(field); + } + + @Test + public void hashCodeAndEqualsShouldWorkOnDifferentClasses() throws Exception { + QualifierDefinition directQualifier1 = QualifierDefinition + .forElement(ReflectionUtils.findField(ConfigA.class, "directQualifier")); + QualifierDefinition directQualifier2 = QualifierDefinition + .forElement(ReflectionUtils.findField(ConfigB.class, "directQualifier")); + QualifierDefinition differentDirectQualifier1 = QualifierDefinition.forElement( + ReflectionUtils.findField(ConfigA.class, "differentDirectQualifier")); + QualifierDefinition differentDirectQualifier2 = QualifierDefinition.forElement( + ReflectionUtils.findField(ConfigB.class, "differentDirectQualifier")); + QualifierDefinition customQualifier1 = QualifierDefinition + .forElement(ReflectionUtils.findField(ConfigA.class, "customQualifier")); + QualifierDefinition customQualifier2 = QualifierDefinition + .forElement(ReflectionUtils.findField(ConfigB.class, "customQualifier")); + assertThat(directQualifier1.hashCode()).isEqualTo(directQualifier2.hashCode()); + assertThat(differentDirectQualifier1.hashCode()) + .isEqualTo(differentDirectQualifier2.hashCode()); + assertThat(customQualifier1.hashCode()).isEqualTo(customQualifier2.hashCode()); + assertThat(differentDirectQualifier1).isEqualTo(differentDirectQualifier1) + .isEqualTo(differentDirectQualifier2).isNotEqualTo(directQualifier2); + assertThat(directQualifier1).isEqualTo(directQualifier1) + .isEqualTo(directQualifier2).isNotEqualTo(differentDirectQualifier1); + assertThat(customQualifier1).isEqualTo(customQualifier1) + .isEqualTo(customQualifier2).isNotEqualTo(differentDirectQualifier1); + } + + @Configuration + static class ConfigA { + + @MockBean + private Object noQualifier; + + @MockBean + @Qualifier("test") + private Object directQualifier; + + @MockBean + @Qualifier("different") + private Object differentDirectQualifier; + + @MockBean + @CustomQualifier + private Object customQualifier; + + } + + static class ConfigB { + + @MockBean + @Qualifier("test") + private Object directQualifier; + + @MockBean + @Qualifier("different") + private Object differentDirectQualifier; + + @MockBean + @CustomQualifier + private Object customQualifier; + + } + + @Qualifier + @Retention(RetentionPolicy.RUNTIME) + public @interface CustomQualifier { + + } + +} diff --git a/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyDefinitionTests.java b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyDefinitionTests.java index de53937694..832916c945 100644 --- a/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyDefinitionTests.java +++ b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyDefinitionTests.java @@ -29,6 +29,7 @@ import org.springframework.boot.test.mock.mockito.example.RealExampleService; import org.springframework.core.ResolvableType; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; /** * Tests for {@link SpyDefinition}. @@ -47,35 +48,36 @@ public class SpyDefinitionTests { public void classToSpyMustNotBeNull() throws Exception { this.thrown.expect(IllegalArgumentException.class); this.thrown.expectMessage("TypeToSpy must not be null"); - new SpyDefinition(null, null, null, null, true); + new SpyDefinition(null, null, null, true, null); } @Test public void createWithDefaults() throws Exception { - SpyDefinition definition = new SpyDefinition(null, null, REAL_SERVICE_TYPE, null, - true); - assertThat(definition.getElement()).isNull(); + SpyDefinition definition = new SpyDefinition(null, REAL_SERVICE_TYPE, null, true, + null); assertThat(definition.getName()).isNull(); assertThat(definition.getTypeToSpy()).isEqualTo(REAL_SERVICE_TYPE); assertThat(definition.getReset()).isEqualTo(MockReset.AFTER); assertThat(definition.isProxyTargetAware()).isTrue(); + assertThat(definition.getQualifier()).isNull(); } @Test public void createExplicit() throws Exception { - SpyDefinition definition = new SpyDefinition(getClass(), "name", - REAL_SERVICE_TYPE, MockReset.BEFORE, false); - assertThat(definition.getElement()).isEqualTo(getClass()); + QualifierDefinition qualifier = mock(QualifierDefinition.class); + SpyDefinition definition = new SpyDefinition("name", REAL_SERVICE_TYPE, + MockReset.BEFORE, false, qualifier); assertThat(definition.getName()).isEqualTo("name"); assertThat(definition.getTypeToSpy()).isEqualTo(REAL_SERVICE_TYPE); assertThat(definition.getReset()).isEqualTo(MockReset.BEFORE); assertThat(definition.isProxyTargetAware()).isFalse(); + assertThat(definition.getQualifier()).isEqualTo(qualifier); } @Test public void createSpy() throws Exception { - SpyDefinition definition = new SpyDefinition(null, "name", REAL_SERVICE_TYPE, - MockReset.BEFORE, true); + SpyDefinition definition = new SpyDefinition("name", REAL_SERVICE_TYPE, + MockReset.BEFORE, true, null); RealExampleService spy = definition.createSpy(new RealExampleService("hello")); MockCreationSettings settings = new MockUtil().getMockSettings(spy); assertThat(spy).isInstanceOf(ExampleService.class); @@ -87,8 +89,8 @@ public class SpyDefinitionTests { @Test public void createSpyWhenNullInstanceShouldThrowException() throws Exception { - SpyDefinition definition = new SpyDefinition(null, "name", REAL_SERVICE_TYPE, - MockReset.BEFORE, true); + SpyDefinition definition = new SpyDefinition("name", REAL_SERVICE_TYPE, + MockReset.BEFORE, true, null); this.thrown.expect(IllegalArgumentException.class); this.thrown.expectMessage("Instance must not be null"); definition.createSpy(null); @@ -96,8 +98,8 @@ public class SpyDefinitionTests { @Test public void createSpyWhenWrongInstanceShouldThrowException() throws Exception { - SpyDefinition definition = new SpyDefinition(null, "name", REAL_SERVICE_TYPE, - MockReset.BEFORE, true); + SpyDefinition definition = new SpyDefinition("name", REAL_SERVICE_TYPE, + MockReset.BEFORE, true, null); this.thrown.expect(IllegalArgumentException.class); this.thrown.expectMessage("must be an instance of"); definition.createSpy(new ExampleServiceCaller(null)); @@ -105,8 +107,8 @@ public class SpyDefinitionTests { @Test public void createSpyTwice() throws Exception { - SpyDefinition definition = new SpyDefinition(null, "name", REAL_SERVICE_TYPE, - MockReset.BEFORE, true); + SpyDefinition definition = new SpyDefinition("name", REAL_SERVICE_TYPE, + MockReset.BEFORE, true, null); Object instance = new RealExampleService("hello"); instance = definition.createSpy(instance); instance = definition.createSpy(instance);