Add value attribute alias to @MockitoBean and @MockitoSpyBean
This commit improves the user experience for common use cases by
introducing new `value` attributes in @MockitoBean and
@MockitoSpyBean that are aliases for the existing `name` attributes.
For example, this allows developers to declare
@MockitoBean("userService") instead of @MockitoBean(name =
"userService"), analogous to the existing name/value alias support in
@TestBean.
Closes gh-33680
This commit is contained in:
@@ -77,7 +77,7 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
class OverrideBeanTests {
|
||||
@MockitoBean(name = "service") // <1>
|
||||
@MockitoBean("service") // <1>
|
||||
private CustomService customService;
|
||||
|
||||
// test case body...
|
||||
@@ -121,7 +121,7 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
class OverrideBeanTests {
|
||||
@MockitoSpyBean(name = "service") // <1>
|
||||
@MockitoSpyBean("service") // <1>
|
||||
private CustomService customService;
|
||||
|
||||
// test case body...
|
||||
|
||||
@@ -25,6 +25,7 @@ import java.lang.annotation.Target;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.MockSettings;
|
||||
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
import org.springframework.test.context.bean.override.BeanOverride;
|
||||
|
||||
/**
|
||||
@@ -33,12 +34,12 @@ import org.springframework.test.context.bean.override.BeanOverride;
|
||||
* {@link org.springframework.context.ApplicationContext ApplicationContext}
|
||||
* using a Mockito mock.
|
||||
*
|
||||
* <p>By default, the bean to override is inferred from the type of the annotated
|
||||
* <p>By default, the bean to mock is inferred from the type of the annotated
|
||||
* field. If multiple candidates exist, a {@code @Qualifier} annotation can be
|
||||
* used to help disambiguate. In the absence of a {@code @Qualifier} annotation,
|
||||
* the name of the annotated field will be used as a fallback qualifier.
|
||||
* Alternatively, you can explicitly specify a bean name to replace by setting the
|
||||
* {@link #name() name} attribute.
|
||||
* Alternatively, you can explicitly specify a bean name to mock by setting the
|
||||
* {@link #value() value} or {@link #name() name} attribute.
|
||||
*
|
||||
* <p>A new bean definition will be created if a corresponding bean definition does
|
||||
* not exist. However, if you would like for the test to fail when a corresponding
|
||||
@@ -52,7 +53,7 @@ import org.springframework.test.context.bean.override.BeanOverride;
|
||||
* the context alongside the existing dependency.
|
||||
*
|
||||
* <p><strong>NOTE</strong>: Only <em>singleton</em> beans can be overridden.
|
||||
* Any attempt to override a non-singleton bean will result in an exception.
|
||||
* Any attempt to mock a non-singleton bean will result in an exception.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @author Sam Brannen
|
||||
@@ -67,12 +68,22 @@ import org.springframework.test.context.bean.override.BeanOverride;
|
||||
public @interface MockitoBean {
|
||||
|
||||
/**
|
||||
* The name of the bean to register or replace.
|
||||
* <p>If left unspecified, the bean to override is selected according to
|
||||
* the annotated field's type, taking qualifiers into account if necessary.
|
||||
* See the {@linkplain MockitoBean class-level documentation} for details.
|
||||
* @return the name of the mocked bean
|
||||
* Alias for {@link #name()}.
|
||||
* <p>Intended to be used when no other attributes are needed — for
|
||||
* example, {@code @MockitoBean("customBeanName")}.
|
||||
* @see #name()
|
||||
*/
|
||||
@AliasFor("name")
|
||||
String value() default "";
|
||||
|
||||
/**
|
||||
* Name of the bean to mock.
|
||||
* <p>If left unspecified, the bean to mock is selected according to the
|
||||
* annotated field's type, taking qualifiers into account if necessary. See
|
||||
* the {@linkplain MockitoBean class-level documentation} for details.
|
||||
* @see #value()
|
||||
*/
|
||||
@AliasFor("value")
|
||||
String name() default "";
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,28 +24,32 @@ import java.lang.annotation.Target;
|
||||
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
import org.springframework.test.context.bean.override.BeanOverride;
|
||||
|
||||
/**
|
||||
* Mark a field to trigger a bean override using a Mockito spy, which will wrap
|
||||
* the original instance.
|
||||
* the original bean instance.
|
||||
*
|
||||
* <p>If no explicit {@link #name()} is specified, a target bean is selected
|
||||
* according to the class of the annotated field, and there must be exactly one
|
||||
* such candidate bean. A {@code @Qualifier} annotation can be used to help
|
||||
* disambiguate.
|
||||
* If a {@link #name()} is specified, it is required that a target bean of that
|
||||
* name has been previously registered in the application context.
|
||||
* <p>By default, the bean to spy is inferred from the type of the annotated
|
||||
* field. If multiple candidates exist, a {@code @Qualifier} annotation can be
|
||||
* used to help disambiguate. In the absence of a {@code @Qualifier} annotation,
|
||||
* the name of the annotated field will be used as a fallback qualifier.
|
||||
* Alternatively, you can explicitly specify a bean name to spy by setting the
|
||||
* {@link #value() value} or {@link #name() name} attribute. If a bean name is
|
||||
* specified, it is required that a target bean with that name has been previously
|
||||
* registered in the application context.
|
||||
*
|
||||
* <p>Dependencies that are known to the application context but are not beans
|
||||
* (such as those
|
||||
* <p>A spy cannot be created for components which are known to the application
|
||||
* context but are not beans — for example, components
|
||||
* {@link org.springframework.beans.factory.config.ConfigurableListableBeanFactory#registerResolvableDependency(Class, Object)
|
||||
* registered directly}) will not be found.
|
||||
* registered directly} as resolvable dependencies.
|
||||
*
|
||||
* <p><strong>NOTE</strong>: Only <em>singleton</em> beans can be overridden.
|
||||
* Any attempt to override a non-singleton bean will result in an exception.
|
||||
* <p><strong>NOTE</strong>: Only <em>singleton</em> beans can be spied.
|
||||
* Any attempt to create a spy for a non-singleton bean will result in an exception.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @author Sam Brannen
|
||||
* @since 6.2
|
||||
* @see org.springframework.test.context.bean.override.mockito.MockitoBean @MockitoBean
|
||||
* @see org.springframework.test.context.bean.override.convention.TestBean @TestBean
|
||||
@@ -57,12 +61,22 @@ import org.springframework.test.context.bean.override.BeanOverride;
|
||||
public @interface MockitoSpyBean {
|
||||
|
||||
/**
|
||||
* The name of the bean to spy.
|
||||
* <p>If left unspecified, the bean to spy is selected according to
|
||||
* the annotated field's type, taking qualifiers into account if necessary.
|
||||
* See the {@linkplain MockitoSpyBean class-level documentation} for details.
|
||||
* @return the name of the spied bean
|
||||
* Alias for {@link #name()}.
|
||||
* <p>Intended to be used when no other attributes are needed — for
|
||||
* example, {@code @MockitoSpyBean("customBeanName")}.
|
||||
* @see #name()
|
||||
*/
|
||||
@AliasFor("name")
|
||||
String value() default "";
|
||||
|
||||
/**
|
||||
* Name of the bean to spy.
|
||||
* <p>If left unspecified, the bean to spy is selected according to the
|
||||
* annotated field's type, taking qualifiers into account if necessary. See
|
||||
* the {@linkplain MockitoSpyBean class-level documentation} for details.
|
||||
* @see #value()
|
||||
*/
|
||||
@AliasFor("value")
|
||||
String name() default "";
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,12 +17,12 @@
|
||||
package org.springframework.test.context.bean.override.mockito;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Answers;
|
||||
|
||||
import org.springframework.test.context.ContextCustomizer;
|
||||
import org.springframework.test.context.bean.override.BeanOverrideContextCustomizerTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Answers.RETURNS_MOCKS;
|
||||
|
||||
/**
|
||||
* Tests that validate the behavior of {@link MockitoBean} and
|
||||
@@ -85,7 +85,7 @@ class MockitoBeanContextCustomizerEqualityTests {
|
||||
|
||||
static class Case1ByName {
|
||||
|
||||
@MockitoBean(name = "serviceBean")
|
||||
@MockitoBean("serviceBean")
|
||||
private String exampleService;
|
||||
|
||||
}
|
||||
@@ -99,7 +99,7 @@ class MockitoBeanContextCustomizerEqualityTests {
|
||||
|
||||
static class Case2ByName {
|
||||
|
||||
@MockitoBean(name = "serviceBean")
|
||||
@MockitoBean("serviceBean")
|
||||
private String serviceToMock;
|
||||
|
||||
}
|
||||
@@ -120,14 +120,14 @@ class MockitoBeanContextCustomizerEqualityTests {
|
||||
|
||||
static class Case3 {
|
||||
|
||||
@MockitoBean(answers = Answers.RETURNS_MOCKS)
|
||||
@MockitoBean(answers = RETURNS_MOCKS)
|
||||
private String exampleService;
|
||||
|
||||
}
|
||||
|
||||
static class Case4ByName {
|
||||
|
||||
@MockitoSpyBean(name = "serviceBean")
|
||||
@MockitoSpyBean("serviceBean")
|
||||
private String exampleService;
|
||||
|
||||
}
|
||||
@@ -141,7 +141,7 @@ class MockitoBeanContextCustomizerEqualityTests {
|
||||
|
||||
static class Case5ByName {
|
||||
|
||||
@MockitoSpyBean(name = "serviceBean")
|
||||
@MockitoSpyBean("serviceBean")
|
||||
private String serviceToMock;
|
||||
|
||||
}
|
||||
|
||||
@@ -36,22 +36,22 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
@SpringJUnitConfig
|
||||
public class MockitoBeanForByNameLookupIntegrationTests {
|
||||
|
||||
@MockitoBean(name = "field")
|
||||
@MockitoBean("field")
|
||||
ExampleService field;
|
||||
|
||||
@MockitoBean(name = "nestedField")
|
||||
@MockitoBean("nestedField")
|
||||
ExampleService nestedField;
|
||||
|
||||
@MockitoBean(name = "field")
|
||||
@MockitoBean("field")
|
||||
ExampleService renamed1;
|
||||
|
||||
@MockitoBean(name = "nestedField")
|
||||
@MockitoBean("nestedField")
|
||||
ExampleService renamed2;
|
||||
|
||||
@MockitoBean(name = "nonExistingBean")
|
||||
@MockitoBean("nonExistingBean")
|
||||
ExampleService nonExisting1;
|
||||
|
||||
@MockitoBean(name = "nestedNonExistingBean")
|
||||
@MockitoBean("nestedNonExistingBean")
|
||||
ExampleService nonExisting2;
|
||||
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Answers;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.test.context.bean.override.OverrideMetadata;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
@@ -114,7 +115,7 @@ class MockitoBeanOverrideMetadataTests {
|
||||
}
|
||||
|
||||
private MockitoBeanOverrideMetadata createMetadata(Field field) {
|
||||
MockitoBean annotation = field.getAnnotation(MockitoBean.class);
|
||||
MockitoBean annotation = AnnotatedElementUtils.getMergedAnnotation(field, MockitoBean.class);
|
||||
return new MockitoBeanOverrideMetadata(field, ResolvableType.forClass(field.getType()), annotation);
|
||||
}
|
||||
|
||||
@@ -128,7 +129,7 @@ class MockitoBeanOverrideMetadataTests {
|
||||
|
||||
static class SampleOneMockWithName {
|
||||
|
||||
@MockitoBean(name = "anotherService")
|
||||
@MockitoBean("anotherService")
|
||||
String service;
|
||||
|
||||
}
|
||||
@@ -144,7 +145,7 @@ class MockitoBeanOverrideMetadataTests {
|
||||
@MockitoBean(name = "beanToMock")
|
||||
private String service3;
|
||||
|
||||
@MockitoBean(name = "beanToMock")
|
||||
@MockitoBean(value = "beanToMock")
|
||||
private String service4;
|
||||
|
||||
@MockitoBean(extraInterfaces = Externalizable.class)
|
||||
|
||||
@@ -26,7 +26,6 @@ import org.junit.jupiter.params.provider.FieldSource;
|
||||
import org.junit.platform.testkit.engine.EngineTestKit;
|
||||
import org.junit.platform.testkit.engine.Events;
|
||||
import org.mockito.exceptions.misusing.UnnecessaryStubbingException;
|
||||
import org.mockito.quality.Strictness;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
@@ -42,6 +41,7 @@ import static org.junit.platform.testkit.engine.TestExecutionResultConditions.me
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.BDDMockito.when;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.quality.Strictness.STRICT_STUBS;
|
||||
|
||||
/**
|
||||
* Integration tests ensuring unnecessary stubbing is reported in various
|
||||
@@ -85,7 +85,7 @@ class MockitoBeanSettingsStrictIntegrationTests {
|
||||
|
||||
@SpringJUnitConfig(Config.class)
|
||||
@DirtiesContext
|
||||
@MockitoBeanSettings(Strictness.STRICT_STUBS)
|
||||
@MockitoBeanSettings(STRICT_STUBS)
|
||||
static class ExplicitStrictness extends BaseCase {
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.springframework.test.context.bean.override.mockito.MockReset.BEFORE;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link MockitoBean} that validate automatic reset
|
||||
@@ -45,10 +46,10 @@ import static org.mockito.Mockito.doReturn;
|
||||
@TestMethodOrder(OrderAnnotation.class)
|
||||
public class MockitoBeanWithResetIntegrationTests {
|
||||
|
||||
@MockitoBean(reset = MockReset.BEFORE)
|
||||
@MockitoBean(reset = BEFORE)
|
||||
ExampleService service;
|
||||
|
||||
@MockitoBean(reset = MockReset.BEFORE)
|
||||
@MockitoBean(reset = BEFORE)
|
||||
FailingExampleService failingService;
|
||||
|
||||
@Order(1)
|
||||
|
||||
@@ -40,16 +40,16 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
@SpringJUnitConfig(Config.class)
|
||||
public class MockitoSpyBeanForByNameLookupIntegrationTests {
|
||||
|
||||
@MockitoSpyBean(name = "field")
|
||||
@MockitoSpyBean("field")
|
||||
ExampleService field;
|
||||
|
||||
@MockitoSpyBean(name = "nestedField")
|
||||
@MockitoSpyBean("nestedField")
|
||||
ExampleService nestedField;
|
||||
|
||||
@MockitoSpyBean(name = "field")
|
||||
@MockitoSpyBean("field")
|
||||
ExampleService renamed1;
|
||||
|
||||
@MockitoSpyBean(name = "nestedField")
|
||||
@MockitoSpyBean("nestedField")
|
||||
ExampleService renamed2;
|
||||
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.List;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.test.context.bean.override.OverrideMetadata;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
@@ -105,7 +106,7 @@ class MockitoSpyBeanOverrideMetadataTests {
|
||||
}
|
||||
|
||||
private MockitoSpyBeanOverrideMetadata createMetadata(Field field) {
|
||||
MockitoSpyBean annotation = field.getAnnotation(MockitoSpyBean.class);
|
||||
MockitoSpyBean annotation = AnnotatedElementUtils.getMergedAnnotation(field, MockitoSpyBean.class);
|
||||
return new MockitoSpyBeanOverrideMetadata(field, ResolvableType.forClass(field.getType()), annotation);
|
||||
}
|
||||
|
||||
@@ -119,7 +120,7 @@ class MockitoSpyBeanOverrideMetadataTests {
|
||||
|
||||
static class SampleOneSpyWithName {
|
||||
|
||||
@MockitoSpyBean(name = "anotherService")
|
||||
@MockitoSpyBean("anotherService")
|
||||
String service;
|
||||
|
||||
}
|
||||
@@ -132,10 +133,10 @@ class MockitoSpyBeanOverrideMetadataTests {
|
||||
@MockitoSpyBean
|
||||
private String service2;
|
||||
|
||||
@MockitoSpyBean(name = "beanToMock")
|
||||
@MockitoSpyBean(name = "beanToSpy")
|
||||
private String service3;
|
||||
|
||||
@MockitoSpyBean(name = "beanToMock")
|
||||
@MockitoSpyBean(value = "beanToSpy")
|
||||
private String service4;
|
||||
|
||||
@MockitoSpyBean(reset = MockReset.BEFORE)
|
||||
|
||||
@@ -81,7 +81,7 @@ class MockitoSpyBeanTests {
|
||||
|
||||
static class ByNameSingleLookup {
|
||||
|
||||
@MockitoSpyBean(name = "beanToSpy")
|
||||
@MockitoSpyBean("beanToSpy")
|
||||
String example;
|
||||
|
||||
}
|
||||
|
||||
@@ -22,11 +22,9 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.context.bean.override.example.ExampleService;
|
||||
import org.springframework.test.context.bean.override.example.FailingExampleService;
|
||||
import org.springframework.test.context.bean.override.example.RealExampleService;
|
||||
@@ -35,6 +33,7 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.springframework.test.context.bean.override.mockito.MockReset.BEFORE;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link MockitoSpyBean} that validate automatic reset
|
||||
@@ -47,12 +46,13 @@ import static org.mockito.Mockito.doReturn;
|
||||
@TestMethodOrder(OrderAnnotation.class)
|
||||
public class MockitoSpyBeanWithResetIntegrationTests {
|
||||
|
||||
@MockitoSpyBean(reset = MockReset.BEFORE)
|
||||
@MockitoSpyBean(reset = BEFORE)
|
||||
ExampleService service;
|
||||
|
||||
@MockitoSpyBean(reset = MockReset.BEFORE)
|
||||
@MockitoSpyBean(name = "failingExampleServiceFactory", reset = BEFORE)
|
||||
FailingExampleService failingService;
|
||||
|
||||
|
||||
@Order(1)
|
||||
@Test
|
||||
void beanFirstEstablishingStub(ApplicationContext ctx) {
|
||||
@@ -83,21 +83,23 @@ public class MockitoSpyBeanWithResetIntegrationTests {
|
||||
@Order(4)
|
||||
@Test
|
||||
void factoryBeanSecondEnsuringStubReset(ApplicationContext ctx) {
|
||||
assertThat(ctx.getBean("factory")).isNotNull().isSameAs(this.failingService);
|
||||
assertThat(ctx.getBean("failingExampleServiceFactory"))
|
||||
.isNotNull()
|
||||
.isSameAs(this.failingService);
|
||||
|
||||
assertThatIllegalStateException().isThrownBy(this.failingService::greeting)
|
||||
.as("not stubbed")
|
||||
.withMessage("Failed");
|
||||
}
|
||||
|
||||
|
||||
static class FailingExampleServiceFactory implements FactoryBean<FailingExampleService> {
|
||||
@Nullable
|
||||
|
||||
@Override
|
||||
public FailingExampleService getObject() {
|
||||
return new FailingExampleService();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return FailingExampleService.class;
|
||||
@@ -107,14 +109,13 @@ public class MockitoSpyBeanWithResetIntegrationTests {
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class Config {
|
||||
|
||||
@Bean("service")
|
||||
ExampleService bean1() {
|
||||
@Bean
|
||||
ExampleService service() {
|
||||
return new RealExampleService("Production hello");
|
||||
}
|
||||
|
||||
@Bean("factory")
|
||||
@Qualifier("factory")
|
||||
FailingExampleServiceFactory factory() {
|
||||
@Bean
|
||||
FailingExampleServiceFactory failingExampleServiceFactory() {
|
||||
return new FailingExampleServiceFactory();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user