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
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
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();
|
||||
|
||||
@@ -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<MockDefinition>(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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<Object> {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user