Skip to content

Commit

Permalink
Add value attribute alias to @⁠MockitoBean and @⁠MockitoSpyBean
Browse files Browse the repository at this point in the history
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
  • Loading branch information
sbrannen committed Oct 10, 2024
1 parent 1b8f2c4 commit 0e8316e
Show file tree
Hide file tree
Showing 12 changed files with 97 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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...
Expand Down Expand Up @@ -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...
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.mockito.Answers;
import org.mockito.MockSettings;

import org.springframework.core.annotation.AliasFor;
import org.springframework.test.context.bean.override.BeanOverride;

/**
Expand All @@ -33,12 +34,12 @@
* {@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
Expand All @@ -52,7 +53,7 @@
* 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
Expand All @@ -67,12 +68,22 @@
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 &mdash; 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 "";

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,28 +24,32 @@

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 &mdash; 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
Expand All @@ -57,12 +61,22 @@
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 &mdash; 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 "";

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -85,7 +85,7 @@ private ContextCustomizer createContextCustomizer(Class<?> testClass) {

static class Case1ByName {

@MockitoBean(name = "serviceBean")
@MockitoBean("serviceBean")
private String exampleService;

}
Expand All @@ -99,7 +99,7 @@ static class Case1ByType {

static class Case2ByName {

@MockitoBean(name = "serviceBean")
@MockitoBean("serviceBean")
private String serviceToMock;

}
Expand All @@ -120,14 +120,14 @@ static class Case2ByTypeSameFieldName {

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;

}
Expand All @@ -141,7 +141,7 @@ static class Case4ByType {

static class Case5ByName {

@MockitoSpyBean(name = "serviceBean")
@MockitoSpyBean("serviceBean")
private String serviceToMock;

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,22 +36,22 @@
@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;


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
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;

Expand Down Expand Up @@ -114,7 +115,7 @@ private Field sampleField(String fieldName) {
}

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

Expand All @@ -128,7 +129,7 @@ static class SampleOneMock {

static class SampleOneMockWithName {

@MockitoBean(name = "anotherService")
@MockitoBean("anotherService")
String service;

}
Expand All @@ -144,7 +145,7 @@ static class Sample {
@MockitoBean(name = "beanToMock")
private String service3;

@MockitoBean(name = "beanToMock")
@MockitoBean(value = "beanToMock")
private String service4;

@MockitoBean(extraInterfaces = Externalizable.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
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;
Expand All @@ -42,6 +41,7 @@
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
Expand Down Expand Up @@ -85,7 +85,7 @@ void unnecessaryStub() {

@SpringJUnitConfig(Config.class)
@DirtiesContext
@MockitoBeanSettings(Strictness.STRICT_STUBS)
@MockitoBeanSettings(STRICT_STUBS)
static class ExplicitStrictness extends BaseCase {
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

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
Expand All @@ -45,10 +46,10 @@
@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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,16 @@
@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;


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
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;

Expand Down Expand Up @@ -105,7 +106,7 @@ private Field sampleField(String fieldName) {
}

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

Expand All @@ -119,7 +120,7 @@ static class SampleOneSpy {

static class SampleOneSpyWithName {

@MockitoSpyBean(name = "anotherService")
@MockitoSpyBean("anotherService")
String service;

}
Expand All @@ -132,10 +133,10 @@ static class Sample {
@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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ static class ByTypeSingleLookup {

static class ByNameSingleLookup {

@MockitoSpyBean(name = "beanToSpy")
@MockitoSpyBean("beanToSpy")
String example;

}
Expand Down
Loading

0 comments on commit 0e8316e

Please sign in to comment.