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 e1dbd0b18c..fff5f0b7f8 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 84407aa6dd..1b16fa2d1e 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 4f76b9f6df..76ae4c0bd2 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 013eb67676..ae52ee62de 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 d666c64441..a816896f92 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
* 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 f040e5f14e..3c88a45b4d 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 5f5d2c54b6..bcd7b49176 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 0000000000..a87f89d9be
--- /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 391381d5e3..e818178e59 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 4c2b0a4b71..f8e1a8c1ce 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
+ * @RunWith(SpringRunner.class)
+ * public class ExampleTests {
+ *
+ * @SpyBean
+ * @Qualifier("example")
+ * private ExampleService service;
+ *
+ * ...
+ * }
+ *
*