From 3f236dc95190c3304d69013888846e4f3c0ce8df Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Fri, 16 Sep 2016 11:17:23 +0200 Subject: [PATCH] Support Qualifiers on MockBean and SpyBean Previously, if an injection point used a qualifier, `MockBean` and `SpyBean` couldn't be used to mock/spy it as there was no way to specify that qualifier information. This commit now detects qualifier information on the injection point and associate it with the created `BeanDefintion`. If one wants to mock a bean that is qualified with `@Qualifier("foo")`, the definition of the mock should be as follows: ``` public class MyTest { @MockBean @Qualifier("foo") private ExampleService service; } ``` As a side effect, it is now possible to mock a service by type even if there are multiple instances of that type in the application context. The provided qualifier information is used to determine the right candidate and the proper bean definition is replaced accordingly. Closes gh-6753 --- .../boot/test/mock/mockito/Definition.java | 15 +++- .../test/mock/mockito/DefinitionsParser.java | 16 ++-- .../boot/test/mock/mockito/MockBean.java | 12 +++ .../test/mock/mockito/MockDefinition.java | 13 +-- .../mock/mockito/MockitoPostProcessor.java | 30 ++++++- .../boot/test/mock/mockito/SpyBean.java | 12 +++ .../boot/test/mock/mockito/SpyDefinition.java | 8 +- .../mock/mockito/DefinitionsParserTests.java | 23 +++-- ...tingBeanWithQualifierIntegrationTests.java | 87 +++++++++++++++++++ .../mock/mockito/MockDefinitionTests.java | 14 +-- .../MockitoContextCustomizerTests.java | 9 +- .../mockito/MockitoPostProcessorTests.java | 42 ++++++++- .../test/mock/mockito/SpyDefinitionTests.java | 18 ++-- .../mock/mockito/example/CustomQualifier.java | 28 ++++++ .../CustomQualifierExampleService.java | 32 +++++++ 15 files changed, 314 insertions(+), 45 deletions(-) create mode 100644 spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnTestFieldForExistingBeanWithQualifierIntegrationTests.java create mode 100644 spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/CustomQualifier.java create mode 100644 spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/CustomQualifierExampleService.java 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 e1dbd0b18c1..fff5f0b7f89 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,6 +16,8 @@ package org.springframework.boot.test.mock.mockito; +import java.lang.reflect.AnnotatedElement; + import org.springframework.util.ObjectUtils; /** @@ -28,18 +30,29 @@ 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(String name, MockReset reset, boolean proxyTargetAware) { + Definition(AnnotatedElement element, String name, MockReset reset, boolean proxyTargetAware) { + this.element = element; 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; + } + /** * Return the name for bean. * @return the name or {@code null} 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 84407aa6dd9..1b16fa2d1ef 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 @@ -38,6 +38,7 @@ import org.springframework.util.StringUtils; * class. * * @author Phillip Webb + * @author Stephane Nicoll */ class DefinitionsParser { @@ -90,10 +91,10 @@ class DefinitionsParser { "The name attribute can only be used when mocking a single class"); } for (ResolvableType typeToMock : typesToMock) { - MockDefinition definition = new MockDefinition(annotation.name(), typeToMock, - annotation.extraInterfaces(), annotation.answer(), + MockDefinition definition = new MockDefinition(element, annotation.name(), + typeToMock, annotation.extraInterfaces(), annotation.answer(), annotation.serializable(), annotation.reset()); - addDefinition(element, definition, "mock"); + addDefinition(definition, "mock"); } } @@ -106,16 +107,17 @@ class DefinitionsParser { "The name attribute can only be used when spying a single class"); } for (ResolvableType typeToSpy : typesToSpy) { - SpyDefinition definition = new SpyDefinition(annotation.name(), typeToSpy, - annotation.reset(), annotation.proxyTargetAware()); - addDefinition(element, definition, "spy"); + SpyDefinition definition = new SpyDefinition(element, annotation.name(), + typeToSpy, annotation.reset(), annotation.proxyTargetAware()); + addDefinition(definition, "spy"); } } - private void addDefinition(AnnotatedElement element, Definition definition, + private void addDefinition(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/MockBean.java b/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockBean.java index 4f76b9f6dfe..76ae4c0bd26 100644 --- a/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockBean.java +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockBean.java @@ -67,6 +67,18 @@ import org.springframework.test.context.junit4.SpringRunner; * * } * + * If there is more than one bean of the requested type, qualifier metadata must be + * specified at field level:
+ * @RunWith(SpringRunner.class)
+ * public class ExampleTests {
+ *
+ *     @MockBean
+ *     @Qualifier("example")
+ *     private ExampleService service;
+ *
+ *     ...
+ * }
+ * 
*

* This annotation is {@code @Repeatable} and may be specified multiple times when working * with Java 8 or contained within an {@link MockBeans @MockBeans} annotation. 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 013eb67676b..ae52ee62dec 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,6 +16,7 @@ package org.springframework.boot.test.mock.mockito; +import java.lang.reflect.AnnotatedElement; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashSet; @@ -49,17 +50,9 @@ class MockDefinition extends Definition { private final boolean serializable; - MockDefinition(Class classToMock) { - this(ResolvableType.forClass(classToMock)); - } - - MockDefinition(ResolvableType typeToMock) { - this(null, typeToMock, null, null, false, null); - } - - MockDefinition(String name, ResolvableType typeToMock, Class[] extraInterfaces, + MockDefinition(AnnotatedElement element, String name, ResolvableType typeToMock, Class[] extraInterfaces, Answers answer, boolean serializable, MockReset reset) { - super(name, reset, false); + super(element, name, reset, false); 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 d666c64441a..a816896f92a 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,6 +17,7 @@ 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; @@ -43,6 +44,7 @@ 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; @@ -72,6 +74,7 @@ import org.springframework.util.StringUtils; * * @author Phillip Webb * @author Andy Wilkinson + * @author Stephane Nicoll * @since 1.4.0 */ public class MockitoPostProcessor extends InstantiationAwareBeanPostProcessorAdapter @@ -206,6 +209,10 @@ public class MockitoPostProcessor extends InstantiationAwareBeanPostProcessorAda definition.setFactoryMethodName("createMock"); definition.getConstructorArgumentValues().addIndexedArgumentValue(0, mockDefinition); + AnnotatedElement element = mockDefinition.getElement(); + if (element instanceof Field) { + definition.setQualifiedElement(element); + } return definition; } @@ -225,8 +232,7 @@ public class MockitoPostProcessor extends InstantiationAwareBeanPostProcessorAda if (StringUtils.hasLength(mockDefinition.getName())) { return mockDefinition.getName(); } - String[] existingBeans = getExistingBeans(beanFactory, - mockDefinition.getTypeToMock()); + String[] existingBeans = findCandidateBeans(beanFactory, mockDefinition); if (ObjectUtils.isEmpty(existingBeans)) { return this.beanNameGenerator.generateBeanName(beanDefinition, registry); } @@ -235,7 +241,7 @@ public class MockitoPostProcessor extends InstantiationAwareBeanPostProcessorAda } throw new IllegalStateException( "Unable to register mock bean " + mockDefinition.getTypeToMock() - + " expected a single existing bean to replace but found " + + " expected a single matching bean to replace but found " + new TreeSet(Arrays.asList(existingBeans))); } @@ -250,6 +256,24 @@ public class MockitoPostProcessor extends InstantiationAwareBeanPostProcessorAda } } + private String[] 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); + } + } + return candidates.toArray(new String[candidates.size()]); + } + return beans; + } + private String[] getExistingBeans(ConfigurableListableBeanFactory beanFactory, ResolvableType type) { Set beans = new LinkedHashSet( diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/SpyBean.java b/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/SpyBean.java index f37c9e1dfc9..980f37e4eff 100644 --- a/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/SpyBean.java +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/SpyBean.java @@ -67,6 +67,18 @@ import org.springframework.test.context.junit4.SpringRunner; * * } * + * If there is more than one bean of the requested type, qualifier metadata must be + * specified at field level:

+ * @RunWith(SpringRunner.class)
+ * public class ExampleTests {
+ *
+ *     @SpyBean
+ *     @Qualifier("example")
+ *     private ExampleService service;
+ *
+ *     ...
+ * }
+ * 
*

* This annotation is {@code @Repeatable} and may be specified multiple times when working * with Java 8 or contained within a {@link SpyBeans @SpyBeans} annotation. 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 f040e5f14ee..3c88a45b4dc 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,6 +16,8 @@ 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; @@ -39,9 +41,9 @@ class SpyDefinition extends Definition { private final ResolvableType typeToSpy; - SpyDefinition(String name, ResolvableType typeToSpy, MockReset reset, - boolean proxyTargetAware) { - super(name, reset, proxyTargetAware); + SpyDefinition(AnnotatedElement element, String name, ResolvableType typeToSpy, + MockReset reset, boolean proxyTargetAware) { + super(element, name, reset, proxyTargetAware); 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 5f5d2c54b67..bcd7b491760 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 @@ -28,6 +28,7 @@ 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; import org.springframework.boot.test.mock.mockito.example.RealExampleService; +import org.springframework.util.ReflectionUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -66,6 +67,7 @@ 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()) @@ -79,9 +81,15 @@ public class DefinitionsParserTests { public void parseMockBeanOnClassAndField() throws Exception { this.parser.parse(MockBeanOnClassAndField.class); assertThat(getDefinitions()).hasSize(2); - assertThat(getMockDefinition(0).getTypeToMock().resolve()) + MockDefinition classDefinition = getMockDefinition(0); + assertThat(classDefinition.getElement()) + .isEqualTo(MockBeanOnClassAndField.class); + assertThat(classDefinition.getTypeToMock().resolve()) .isEqualTo(ExampleService.class); - assertThat(getMockDefinition(1).getTypeToMock().resolve()) + MockDefinition fieldDefinition = getMockDefinition(1); + assertThat(fieldDefinition.getElement()).isEqualTo( + ReflectionUtils.findField(MockBeanOnClassAndField.class, "caller")); + assertThat(fieldDefinition.getTypeToMock().resolve()) .isEqualTo(ExampleServiceCaller.class); } @@ -141,6 +149,7 @@ 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); @@ -151,10 +160,14 @@ public class DefinitionsParserTests { public void parseSpyBeanOnClassAndField() throws Exception { this.parser.parse(SpyBeanOnClassAndField.class); assertThat(getDefinitions()).hasSize(2); - assertThat(getSpyDefinition(0).getTypeToSpy().resolve()) + SpyDefinition classDefinition = getSpyDefinition(0); + assertThat(classDefinition.getElement()) + .isEqualTo(SpyBeanOnClassAndField.class); + assertThat(classDefinition.getTypeToSpy().resolve()) .isEqualTo(RealExampleService.class); - assertThat(getSpyDefinition(1).getTypeToSpy().resolve()) - .isEqualTo(ExampleServiceCaller.class); + SpyDefinition fieldDefinition = getSpyDefinition(1); + assertThat(fieldDefinition.getElement()).isEqualTo( + ReflectionUtils.findField(SpyBeanOnClassAndField.class, "caller")); } @Test diff --git a/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnTestFieldForExistingBeanWithQualifierIntegrationTests.java b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnTestFieldForExistingBeanWithQualifierIntegrationTests.java new file mode 100644 index 00000000000..a87f89d9be1 --- /dev/null +++ b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnTestFieldForExistingBeanWithQualifierIntegrationTests.java @@ -0,0 +1,87 @@ +/* + * 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.CustomQualifier; +import org.springframework.boot.test.mock.mockito.example.CustomQualifierExampleService; +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.RealExampleService; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verify; + +/** + * Test {@link MockBean} on a test class field can be used to replace existing bean + * while preserving qualifiers. + */ +@RunWith(SpringRunner.class) +public class MockBeanOnTestFieldForExistingBeanWithQualifierIntegrationTests { + + @MockBean + @CustomQualifier + private ExampleService service; + + @Autowired + private ExampleServiceCaller caller; + + @Autowired + private ApplicationContext applicationContext; + + @Test + public void testMocking() throws Exception { + this.caller.sayGreeting(); + verify(this.service).greeting(); + } + + @Test + public void onlyQualifiedBeanIsReplaced() { + assertThat(this.applicationContext.getBean("service")).isSameAs(this.service); + ExampleService anotherService = this.applicationContext.getBean( + "anotherService", ExampleService.class); + assertThat(anotherService.greeting()).isEqualTo("Another"); + } + + @Configuration + static class TestConfig { + + @Bean + public CustomQualifierExampleService service() { + return new CustomQualifierExampleService(); + } + + @Bean + public ExampleService anotherService() { + return new RealExampleService("Another"); + } + + @Bean + public ExampleServiceCaller controller(@CustomQualifier ExampleService service) { + return new ExampleServiceCaller(service); + } + + } + +} 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 391381d5e3a..e818178e597 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 @@ -46,13 +46,14 @@ 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, false, null); + new MockDefinition(null, null, null, null, null, false, null); } @Test public void createWithDefaults() throws Exception { - MockDefinition definition = new MockDefinition(null, EXAMPLE_SERVICE_TYPE, null, - null, false, null); + MockDefinition definition = new MockDefinition(null, null, EXAMPLE_SERVICE_TYPE, + null, null, false, null); + assertThat(definition.getElement()).isNull(); assertThat(definition.getName()).isNull(); assertThat(definition.getTypeToMock()).isEqualTo(EXAMPLE_SERVICE_TYPE); assertThat(definition.getExtraInterfaces()).isEmpty(); @@ -63,9 +64,11 @@ public class MockDefinitionTests { @Test public void createExplicit() throws Exception { - MockDefinition definition = new MockDefinition("name", EXAMPLE_SERVICE_TYPE, + 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()); assertThat(definition.getName()).isEqualTo("name"); assertThat(definition.getTypeToMock()).isEqualTo(EXAMPLE_SERVICE_TYPE); assertThat(definition.getExtraInterfaces()) @@ -78,7 +81,8 @@ public class MockDefinitionTests { @Test public void createMock() throws Exception { - MockDefinition definition = new MockDefinition("name", EXAMPLE_SERVICE_TYPE, + MockDefinition definition = new MockDefinition(null, "name", + EXAMPLE_SERVICE_TYPE, new Class[] { ExampleExtraInterface.class }, Answers.RETURNS_SMART_NULLS, true, MockReset.BEFORE); ExampleService mock = definition.createMock(); 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 4c2b0a4b715..f8e1a8c1ce4 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 @@ -25,6 +25,7 @@ import org.junit.Test; import org.springframework.boot.test.mock.mockito.example.ExampleService; import org.springframework.boot.test.mock.mockito.example.ExampleServiceCaller; +import org.springframework.core.ResolvableType; import static org.assertj.core.api.Assertions.assertThat; @@ -39,8 +40,8 @@ public class MockitoContextCustomizerTests { @Test public void hashCodeAndEquals() { - MockDefinition d1 = new MockDefinition(ExampleService.class); - MockDefinition d2 = new MockDefinition(ExampleServiceCaller.class); + MockDefinition d1 = createTestMockDefinition(ExampleService.class); + MockDefinition d2 = createTestMockDefinition(ExampleServiceCaller.class); MockitoContextCustomizer c1 = new MockitoContextCustomizer(NO_DEFINITIONS); MockitoContextCustomizer c2 = new MockitoContextCustomizer( new LinkedHashSet(Arrays.asList(d1, d2))); @@ -51,4 +52,8 @@ public class MockitoContextCustomizerTests { assertThat(c2).isEqualTo(c2).isEqualTo(c3).isNotEqualTo(c1); } + private MockDefinition createTestMockDefinition(Class typeToMock) { + return new MockDefinition(null, null, ResolvableType.forClass(typeToMock), null, null, false, null); + } + } diff --git a/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoPostProcessorTests.java b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoPostProcessorTests.java index a88c6b98241..8bfc6d9b1ff 100644 --- a/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoPostProcessorTests.java +++ b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoPostProcessorTests.java @@ -22,6 +22,7 @@ import org.junit.rules.ExpectedException; import org.mockito.internal.util.MockUtil; import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.boot.test.mock.mockito.example.ExampleService; import org.springframework.boot.test.mock.mockito.example.FailingExampleService; @@ -50,11 +51,24 @@ public class MockitoPostProcessorTests { this.thrown.expect(IllegalStateException.class); this.thrown.expectMessage( "Unable to register mock bean " + ExampleService.class.getName() - + " expected a single existing bean to replace " + + " expected a single matching bean to replace " + "but found [example1, example2]"); context.refresh(); } + @Test + public void cannotMockMultipleQualifiedBeans() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + MockitoPostProcessor.register(context); + context.register(MultipleQualifiedBeans.class); + this.thrown.expect(IllegalStateException.class); + this.thrown.expectMessage( + "Unable to register mock bean " + ExampleService.class.getName() + + " expected a single matching bean to replace " + + "but found [example1, example3]"); + context.refresh(); + } + @Test public void canMockBeanProducedByFactoryBeanWithObjectTypeAttribute() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); @@ -96,6 +110,32 @@ public class MockitoPostProcessorTests { } + @Configuration + static class MultipleQualifiedBeans { + + @MockBean(ExampleService.class) + @Qualifier("test") + private ExampleService mock; + + @Bean + @Qualifier("test") + public ExampleService example1() { + return new FailingExampleService(); + } + + @Bean + public ExampleService example2() { + return new FailingExampleService(); + } + + @Bean + @Qualifier("test") + public ExampleService example3() { + return new FailingExampleService(); + } + + } + static class TestFactoryBean implements FactoryBean { @Override 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 fb738f52aca..91399c7adbd 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 @@ -47,12 +47,13 @@ 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, true); + new SpyDefinition(null, null, null, null, true); } @Test public void createWithDefaults() throws Exception { - SpyDefinition definition = new SpyDefinition(null, REAL_SERVICE_TYPE, null, true); + SpyDefinition definition = new SpyDefinition(null, null, REAL_SERVICE_TYPE, null, true); + assertThat(definition.getElement()).isNull(); assertThat(definition.getName()).isNull(); assertThat(definition.getTypeToSpy()).isEqualTo(REAL_SERVICE_TYPE); assertThat(definition.getReset()).isEqualTo(MockReset.AFTER); @@ -61,8 +62,9 @@ public class SpyDefinitionTests { @Test public void createExplicit() throws Exception { - SpyDefinition definition = new SpyDefinition("name", REAL_SERVICE_TYPE, - MockReset.BEFORE, false); + SpyDefinition definition = new SpyDefinition(getClass(), "name", + REAL_SERVICE_TYPE, MockReset.BEFORE, false); + assertThat(definition.getElement()).isEqualTo(getClass()); assertThat(definition.getName()).isEqualTo("name"); assertThat(definition.getTypeToSpy()).isEqualTo(REAL_SERVICE_TYPE); assertThat(definition.getReset()).isEqualTo(MockReset.BEFORE); @@ -71,7 +73,7 @@ public class SpyDefinitionTests { @Test public void createSpy() throws Exception { - SpyDefinition definition = new SpyDefinition("name", REAL_SERVICE_TYPE, + SpyDefinition definition = new SpyDefinition(null, "name", REAL_SERVICE_TYPE, MockReset.BEFORE, true); RealExampleService spy = definition.createSpy(new RealExampleService("hello")); MockCreationSettings settings = new MockUtil().getMockSettings(spy); @@ -84,7 +86,7 @@ public class SpyDefinitionTests { @Test public void createSpyWhenNullInstanceShouldThrowException() throws Exception { - SpyDefinition definition = new SpyDefinition("name", REAL_SERVICE_TYPE, + SpyDefinition definition = new SpyDefinition(null, "name", REAL_SERVICE_TYPE, MockReset.BEFORE, true); this.thrown.expect(IllegalArgumentException.class); this.thrown.expectMessage("Instance must not be null"); @@ -93,7 +95,7 @@ public class SpyDefinitionTests { @Test public void createSpyWhenWrongInstanceShouldThrowException() throws Exception { - SpyDefinition definition = new SpyDefinition("name", REAL_SERVICE_TYPE, + SpyDefinition definition = new SpyDefinition(null, "name", REAL_SERVICE_TYPE, MockReset.BEFORE, true); this.thrown.expect(IllegalArgumentException.class); this.thrown.expectMessage("must be an instance of"); @@ -102,7 +104,7 @@ public class SpyDefinitionTests { @Test public void createSpyTwice() throws Exception { - SpyDefinition definition = new SpyDefinition("name", REAL_SERVICE_TYPE, + SpyDefinition definition = new SpyDefinition(null, "name", REAL_SERVICE_TYPE, MockReset.BEFORE, true); Object instance = new RealExampleService("hello"); instance = definition.createSpy(instance); diff --git a/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/CustomQualifier.java b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/CustomQualifier.java new file mode 100644 index 00000000000..89fbd166cd2 --- /dev/null +++ b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/CustomQualifier.java @@ -0,0 +1,28 @@ +/* + * 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; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import org.springframework.beans.factory.annotation.Qualifier; + +@Qualifier +@Retention(RetentionPolicy.RUNTIME) +public @interface CustomQualifier { + +} diff --git a/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/CustomQualifierExampleService.java b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/CustomQualifierExampleService.java new file mode 100644 index 00000000000..44d29c5218e --- /dev/null +++ b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/CustomQualifierExampleService.java @@ -0,0 +1,32 @@ +/* + * 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; + +/** + * An {@link ExampleService} that uses a custom qualifier. + * + * @author Andy Wilkinson + */ +@CustomQualifier +public class CustomQualifierExampleService implements ExampleService { + + @Override + public String greeting() { + return "CustomQualifier"; + } + +}