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
pull/6909/head
Stephane Nicoll 8 years ago
parent 3e19f8aa8d
commit 3f236dc951

@ -16,6 +16,8 @@
package org.springframework.boot.test.mock.mockito; package org.springframework.boot.test.mock.mockito;
import java.lang.reflect.AnnotatedElement;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
/** /**
@ -28,18 +30,29 @@ abstract class Definition {
private static final int MULTIPLIER = 31; private static final int MULTIPLIER = 31;
private final AnnotatedElement element;
private final String name; private final String name;
private final MockReset reset; private final MockReset reset;
private final boolean proxyTargetAware; 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.name = name;
this.reset = (reset != null ? reset : MockReset.AFTER); this.reset = (reset != null ? reset : MockReset.AFTER);
this.proxyTargetAware = proxyTargetAware; 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 for bean.
* @return the name or {@code null} * @return the name or {@code null}

@ -38,6 +38,7 @@ import org.springframework.util.StringUtils;
* class. * class.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Stephane Nicoll
*/ */
class DefinitionsParser { class DefinitionsParser {
@ -90,10 +91,10 @@ class DefinitionsParser {
"The name attribute can only be used when mocking a single class"); "The name attribute can only be used when mocking a single class");
} }
for (ResolvableType typeToMock : typesToMock) { for (ResolvableType typeToMock : typesToMock) {
MockDefinition definition = new MockDefinition(annotation.name(), typeToMock, MockDefinition definition = new MockDefinition(element, annotation.name(),
annotation.extraInterfaces(), annotation.answer(), typeToMock, annotation.extraInterfaces(), annotation.answer(),
annotation.serializable(), annotation.reset()); 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"); "The name attribute can only be used when spying a single class");
} }
for (ResolvableType typeToSpy : typesToSpy) { for (ResolvableType typeToSpy : typesToSpy) {
SpyDefinition definition = new SpyDefinition(annotation.name(), typeToSpy, SpyDefinition definition = new SpyDefinition(element, annotation.name(),
annotation.reset(), annotation.proxyTargetAware()); typeToSpy, annotation.reset(), annotation.proxyTargetAware());
addDefinition(element, definition, "spy"); addDefinition(definition, "spy");
} }
} }
private void addDefinition(AnnotatedElement element, Definition definition, private void addDefinition(Definition definition,
String type) { String type) {
boolean isNewDefinition = this.definitions.add(definition); boolean isNewDefinition = this.definitions.add(definition);
Assert.state(isNewDefinition, "Duplicate " + type + " definition " + definition); Assert.state(isNewDefinition, "Duplicate " + type + " definition " + definition);
AnnotatedElement element = definition.getElement();
if (element instanceof Field) { if (element instanceof Field) {
Field field = (Field) element; Field field = (Field) element;
this.definitionFields.put(definition, field); this.definitionFields.put(definition, field);

@ -67,6 +67,18 @@ import org.springframework.test.context.junit4.SpringRunner;
* *
* } * }
* </pre> * </pre>
* If there is more than one bean of the requested type, qualifier metadata must be
* specified at field level: <pre class="code">
* &#064;RunWith(SpringRunner.class)
* public class ExampleTests {
*
* &#064;MockBean
* &#064;Qualifier("example")
* private ExampleService service;
*
* ...
* }
* </pre>
* <p> * <p>
* This annotation is {@code @Repeatable} and may be specified multiple times when working * This annotation is {@code @Repeatable} and may be specified multiple times when working
* with Java 8 or contained within an {@link MockBeans @MockBeans} annotation. * with Java 8 or contained within an {@link MockBeans @MockBeans} annotation.

@ -16,6 +16,7 @@
package org.springframework.boot.test.mock.mockito; package org.springframework.boot.test.mock.mockito;
import java.lang.reflect.AnnotatedElement;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
@ -49,17 +50,9 @@ class MockDefinition extends Definition {
private final boolean serializable; private final boolean serializable;
MockDefinition(Class<?> classToMock) { MockDefinition(AnnotatedElement element, String name, ResolvableType typeToMock, Class<?>[] extraInterfaces,
this(ResolvableType.forClass(classToMock));
}
MockDefinition(ResolvableType typeToMock) {
this(null, typeToMock, null, null, false, null);
}
MockDefinition(String name, ResolvableType typeToMock, Class<?>[] extraInterfaces,
Answers answer, boolean serializable, MockReset reset) { Answers answer, boolean serializable, MockReset reset) {
super(name, reset, false); super(element, name, reset, false);
Assert.notNull(typeToMock, "TypeToMock must not be null"); Assert.notNull(typeToMock, "TypeToMock must not be null");
this.typeToMock = typeToMock; this.typeToMock = typeToMock;
this.extraInterfaces = asClassSet(extraInterfaces); this.extraInterfaces = asClassSet(extraInterfaces);

@ -17,6 +17,7 @@
package org.springframework.boot.test.mock.mockito; package org.springframework.boot.test.mock.mockito;
import java.beans.PropertyDescriptor; import java.beans.PropertyDescriptor;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; 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.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.ConstructorArgumentValues; import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder; 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.InstantiationAwareBeanPostProcessorAdapter;
import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
@ -72,6 +74,7 @@ import org.springframework.util.StringUtils;
* *
* @author Phillip Webb * @author Phillip Webb
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Stephane Nicoll
* @since 1.4.0 * @since 1.4.0
*/ */
public class MockitoPostProcessor extends InstantiationAwareBeanPostProcessorAdapter public class MockitoPostProcessor extends InstantiationAwareBeanPostProcessorAdapter
@ -206,6 +209,10 @@ public class MockitoPostProcessor extends InstantiationAwareBeanPostProcessorAda
definition.setFactoryMethodName("createMock"); definition.setFactoryMethodName("createMock");
definition.getConstructorArgumentValues().addIndexedArgumentValue(0, definition.getConstructorArgumentValues().addIndexedArgumentValue(0,
mockDefinition); mockDefinition);
AnnotatedElement element = mockDefinition.getElement();
if (element instanceof Field) {
definition.setQualifiedElement(element);
}
return definition; return definition;
} }
@ -225,8 +232,7 @@ public class MockitoPostProcessor extends InstantiationAwareBeanPostProcessorAda
if (StringUtils.hasLength(mockDefinition.getName())) { if (StringUtils.hasLength(mockDefinition.getName())) {
return mockDefinition.getName(); return mockDefinition.getName();
} }
String[] existingBeans = getExistingBeans(beanFactory, String[] existingBeans = findCandidateBeans(beanFactory, mockDefinition);
mockDefinition.getTypeToMock());
if (ObjectUtils.isEmpty(existingBeans)) { if (ObjectUtils.isEmpty(existingBeans)) {
return this.beanNameGenerator.generateBeanName(beanDefinition, registry); return this.beanNameGenerator.generateBeanName(beanDefinition, registry);
} }
@ -235,7 +241,7 @@ public class MockitoPostProcessor extends InstantiationAwareBeanPostProcessorAda
} }
throw new IllegalStateException( throw new IllegalStateException(
"Unable to register mock bean " + mockDefinition.getTypeToMock() "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<String>(Arrays.asList(existingBeans))); + new TreeSet<String>(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<String> candidates = new LinkedHashSet<String>();
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, private String[] getExistingBeans(ConfigurableListableBeanFactory beanFactory,
ResolvableType type) { ResolvableType type) {
Set<String> beans = new LinkedHashSet<String>( Set<String> beans = new LinkedHashSet<String>(

@ -67,6 +67,18 @@ import org.springframework.test.context.junit4.SpringRunner;
* *
* } * }
* </pre> * </pre>
* If there is more than one bean of the requested type, qualifier metadata must be
* specified at field level: <pre class="code">
* &#064;RunWith(SpringRunner.class)
* public class ExampleTests {
*
* &#064;SpyBean
* &#064;Qualifier("example")
* private ExampleService service;
*
* ...
* }
* </pre>
* <p> * <p>
* This annotation is {@code @Repeatable} and may be specified multiple times when working * This annotation is {@code @Repeatable} and may be specified multiple times when working
* with Java 8 or contained within a {@link SpyBeans @SpyBeans} annotation. * with Java 8 or contained within a {@link SpyBeans @SpyBeans} annotation.

@ -16,6 +16,8 @@
package org.springframework.boot.test.mock.mockito; package org.springframework.boot.test.mock.mockito;
import java.lang.reflect.AnnotatedElement;
import org.mockito.MockSettings; import org.mockito.MockSettings;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.internal.util.MockUtil; import org.mockito.internal.util.MockUtil;
@ -39,9 +41,9 @@ class SpyDefinition extends Definition {
private final ResolvableType typeToSpy; private final ResolvableType typeToSpy;
SpyDefinition(String name, ResolvableType typeToSpy, MockReset reset, SpyDefinition(AnnotatedElement element, String name, ResolvableType typeToSpy,
boolean proxyTargetAware) { MockReset reset, boolean proxyTargetAware) {
super(name, reset, proxyTargetAware); super(element, name, reset, proxyTargetAware);
Assert.notNull(typeToSpy, "TypeToSpy must not be null"); Assert.notNull(typeToSpy, "TypeToSpy must not be null");
this.typeToSpy = typeToSpy; this.typeToSpy = typeToSpy;

@ -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.ExampleService;
import org.springframework.boot.test.mock.mockito.example.ExampleServiceCaller; import org.springframework.boot.test.mock.mockito.example.ExampleServiceCaller;
import org.springframework.boot.test.mock.mockito.example.RealExampleService; import org.springframework.boot.test.mock.mockito.example.RealExampleService;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -66,6 +67,7 @@ public class DefinitionsParserTests {
this.parser.parse(MockBeanAttributes.class); this.parser.parse(MockBeanAttributes.class);
assertThat(getDefinitions()).hasSize(1); assertThat(getDefinitions()).hasSize(1);
MockDefinition definition = getMockDefinition(0); MockDefinition definition = getMockDefinition(0);
assertThat(definition.getElement()).isEqualTo(MockBeanAttributes.class);
assertThat(definition.getName()).isEqualTo("Name"); assertThat(definition.getName()).isEqualTo("Name");
assertThat(definition.getTypeToMock().resolve()).isEqualTo(ExampleService.class); assertThat(definition.getTypeToMock().resolve()).isEqualTo(ExampleService.class);
assertThat(definition.getExtraInterfaces()) assertThat(definition.getExtraInterfaces())
@ -79,9 +81,15 @@ public class DefinitionsParserTests {
public void parseMockBeanOnClassAndField() throws Exception { public void parseMockBeanOnClassAndField() throws Exception {
this.parser.parse(MockBeanOnClassAndField.class); this.parser.parse(MockBeanOnClassAndField.class);
assertThat(getDefinitions()).hasSize(2); 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); .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); .isEqualTo(ExampleServiceCaller.class);
} }
@ -141,6 +149,7 @@ public class DefinitionsParserTests {
this.parser.parse(SpyBeanAttributes.class); this.parser.parse(SpyBeanAttributes.class);
assertThat(getDefinitions()).hasSize(1); assertThat(getDefinitions()).hasSize(1);
SpyDefinition definition = getSpyDefinition(0); SpyDefinition definition = getSpyDefinition(0);
assertThat(definition.getElement()).isEqualTo(SpyBeanAttributes.class);
assertThat(definition.getName()).isEqualTo("Name"); assertThat(definition.getName()).isEqualTo("Name");
assertThat(definition.getTypeToSpy().resolve()) assertThat(definition.getTypeToSpy().resolve())
.isEqualTo(RealExampleService.class); .isEqualTo(RealExampleService.class);
@ -151,10 +160,14 @@ public class DefinitionsParserTests {
public void parseSpyBeanOnClassAndField() throws Exception { public void parseSpyBeanOnClassAndField() throws Exception {
this.parser.parse(SpyBeanOnClassAndField.class); this.parser.parse(SpyBeanOnClassAndField.class);
assertThat(getDefinitions()).hasSize(2); 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); .isEqualTo(RealExampleService.class);
assertThat(getSpyDefinition(1).getTypeToSpy().resolve()) SpyDefinition fieldDefinition = getSpyDefinition(1);
.isEqualTo(ExampleServiceCaller.class); assertThat(fieldDefinition.getElement()).isEqualTo(
ReflectionUtils.findField(SpyBeanOnClassAndField.class, "caller"));
} }
@Test @Test

@ -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);
}
}
}

@ -46,13 +46,14 @@ public class MockDefinitionTests {
public void classToMockMustNotBeNull() throws Exception { public void classToMockMustNotBeNull() throws Exception {
this.thrown.expect(IllegalArgumentException.class); this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("TypeToMock must not be null"); 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 @Test
public void createWithDefaults() throws Exception { public void createWithDefaults() throws Exception {
MockDefinition definition = new MockDefinition(null, EXAMPLE_SERVICE_TYPE, null, MockDefinition definition = new MockDefinition(null, null, EXAMPLE_SERVICE_TYPE,
null, false, null); null, null, false, null);
assertThat(definition.getElement()).isNull();
assertThat(definition.getName()).isNull(); assertThat(definition.getName()).isNull();
assertThat(definition.getTypeToMock()).isEqualTo(EXAMPLE_SERVICE_TYPE); assertThat(definition.getTypeToMock()).isEqualTo(EXAMPLE_SERVICE_TYPE);
assertThat(definition.getExtraInterfaces()).isEmpty(); assertThat(definition.getExtraInterfaces()).isEmpty();
@ -63,9 +64,11 @@ public class MockDefinitionTests {
@Test @Test
public void createExplicit() throws Exception { 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 }, new Class<?>[] { ExampleExtraInterface.class },
Answers.RETURNS_SMART_NULLS, true, MockReset.BEFORE); Answers.RETURNS_SMART_NULLS, true, MockReset.BEFORE);
assertThat(definition.getElement()).isEqualTo(getClass());
assertThat(definition.getName()).isEqualTo("name"); assertThat(definition.getName()).isEqualTo("name");
assertThat(definition.getTypeToMock()).isEqualTo(EXAMPLE_SERVICE_TYPE); assertThat(definition.getTypeToMock()).isEqualTo(EXAMPLE_SERVICE_TYPE);
assertThat(definition.getExtraInterfaces()) assertThat(definition.getExtraInterfaces())
@ -78,7 +81,8 @@ public class MockDefinitionTests {
@Test @Test
public void createMock() throws Exception { 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 }, new Class<?>[] { ExampleExtraInterface.class },
Answers.RETURNS_SMART_NULLS, true, MockReset.BEFORE); Answers.RETURNS_SMART_NULLS, true, MockReset.BEFORE);
ExampleService mock = definition.createMock(); ExampleService mock = definition.createMock();

@ -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.ExampleService;
import org.springframework.boot.test.mock.mockito.example.ExampleServiceCaller; import org.springframework.boot.test.mock.mockito.example.ExampleServiceCaller;
import org.springframework.core.ResolvableType;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -39,8 +40,8 @@ public class MockitoContextCustomizerTests {
@Test @Test
public void hashCodeAndEquals() { public void hashCodeAndEquals() {
MockDefinition d1 = new MockDefinition(ExampleService.class); MockDefinition d1 = createTestMockDefinition(ExampleService.class);
MockDefinition d2 = new MockDefinition(ExampleServiceCaller.class); MockDefinition d2 = createTestMockDefinition(ExampleServiceCaller.class);
MockitoContextCustomizer c1 = new MockitoContextCustomizer(NO_DEFINITIONS); MockitoContextCustomizer c1 = new MockitoContextCustomizer(NO_DEFINITIONS);
MockitoContextCustomizer c2 = new MockitoContextCustomizer( MockitoContextCustomizer c2 = new MockitoContextCustomizer(
new LinkedHashSet<MockDefinition>(Arrays.asList(d1, d2))); new LinkedHashSet<MockDefinition>(Arrays.asList(d1, d2)));
@ -51,4 +52,8 @@ public class MockitoContextCustomizerTests {
assertThat(c2).isEqualTo(c2).isEqualTo(c3).isNotEqualTo(c1); 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);
}
} }

@ -22,6 +22,7 @@ import org.junit.rules.ExpectedException;
import org.mockito.internal.util.MockUtil; import org.mockito.internal.util.MockUtil;
import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.test.mock.mockito.example.ExampleService; import org.springframework.boot.test.mock.mockito.example.ExampleService;
import org.springframework.boot.test.mock.mockito.example.FailingExampleService; import org.springframework.boot.test.mock.mockito.example.FailingExampleService;
@ -50,11 +51,24 @@ public class MockitoPostProcessorTests {
this.thrown.expect(IllegalStateException.class); this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage( this.thrown.expectMessage(
"Unable to register mock bean " + ExampleService.class.getName() "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]"); + "but found [example1, example2]");
context.refresh(); 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 @Test
public void canMockBeanProducedByFactoryBeanWithObjectTypeAttribute() { public void canMockBeanProducedByFactoryBeanWithObjectTypeAttribute() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); 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<Object> { static class TestFactoryBean implements FactoryBean<Object> {
@Override @Override

@ -47,12 +47,13 @@ public class SpyDefinitionTests {
public void classToSpyMustNotBeNull() throws Exception { public void classToSpyMustNotBeNull() throws Exception {
this.thrown.expect(IllegalArgumentException.class); this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("TypeToSpy must not be null"); this.thrown.expectMessage("TypeToSpy must not be null");
new SpyDefinition(null, null, null, true); new SpyDefinition(null, null, null, null, true);
} }
@Test @Test
public void createWithDefaults() throws Exception { 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.getName()).isNull();
assertThat(definition.getTypeToSpy()).isEqualTo(REAL_SERVICE_TYPE); assertThat(definition.getTypeToSpy()).isEqualTo(REAL_SERVICE_TYPE);
assertThat(definition.getReset()).isEqualTo(MockReset.AFTER); assertThat(definition.getReset()).isEqualTo(MockReset.AFTER);
@ -61,8 +62,9 @@ public class SpyDefinitionTests {
@Test @Test
public void createExplicit() throws Exception { public void createExplicit() throws Exception {
SpyDefinition definition = new SpyDefinition("name", REAL_SERVICE_TYPE, SpyDefinition definition = new SpyDefinition(getClass(), "name",
MockReset.BEFORE, false); REAL_SERVICE_TYPE, MockReset.BEFORE, false);
assertThat(definition.getElement()).isEqualTo(getClass());
assertThat(definition.getName()).isEqualTo("name"); assertThat(definition.getName()).isEqualTo("name");
assertThat(definition.getTypeToSpy()).isEqualTo(REAL_SERVICE_TYPE); assertThat(definition.getTypeToSpy()).isEqualTo(REAL_SERVICE_TYPE);
assertThat(definition.getReset()).isEqualTo(MockReset.BEFORE); assertThat(definition.getReset()).isEqualTo(MockReset.BEFORE);
@ -71,7 +73,7 @@ public class SpyDefinitionTests {
@Test @Test
public void createSpy() throws Exception { 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); MockReset.BEFORE, true);
RealExampleService spy = definition.createSpy(new RealExampleService("hello")); RealExampleService spy = definition.createSpy(new RealExampleService("hello"));
MockCreationSettings<?> settings = new MockUtil().getMockSettings(spy); MockCreationSettings<?> settings = new MockUtil().getMockSettings(spy);
@ -84,7 +86,7 @@ public class SpyDefinitionTests {
@Test @Test
public void createSpyWhenNullInstanceShouldThrowException() throws Exception { 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); MockReset.BEFORE, true);
this.thrown.expect(IllegalArgumentException.class); this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Instance must not be null"); this.thrown.expectMessage("Instance must not be null");
@ -93,7 +95,7 @@ public class SpyDefinitionTests {
@Test @Test
public void createSpyWhenWrongInstanceShouldThrowException() throws Exception { 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); MockReset.BEFORE, true);
this.thrown.expect(IllegalArgumentException.class); this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("must be an instance of"); this.thrown.expectMessage("must be an instance of");
@ -102,7 +104,7 @@ public class SpyDefinitionTests {
@Test @Test
public void createSpyTwice() throws Exception { 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); MockReset.BEFORE, true);
Object instance = new RealExampleService("hello"); Object instance = new RealExampleService("hello");
instance = definition.createSpy(instance); instance = definition.createSpy(instance);

@ -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 {
}

@ -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";
}
}
Loading…
Cancel
Save