mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-22 05:49:04 +00:00
Support @MockitoBean and @MockitoSpyBean on test constructor parameters
Prior to this commit, @MockitoBean and @MockitoSpyBean could be
declared on fields or at the type level (on test classes and test
interfaces), but not on constructor parameters. Consequently, a test
class could not use constructor injection for bean overrides.
To address that, this commit introduces support for @MockitoBean and
@MockitoSpyBean on constructor parameters in JUnit Jupiter test
classes. Specifically, the Bean Override infrastructure has been
overhauled to support constructor parameters as declaration sites and
injection points alongside fields, and the SpringExtension now
recognizes composed @BeanOverride annotations on constructor
parameters in supportsParameter() and resolves them properly in
resolveParameter(). Note, however, that this support has not been
introduced for @TestBean.
For example, the following which uses field injection:
@SpringJUnitConfig(TestConfig.class)
class BeanOverrideTests {
@MockitoBean
CustomService customService;
// tests...
}
Can now be rewritten to use constructor injection:
@SpringJUnitConfig(TestConfig.class)
class BeanOverrideTests {
private final CustomService customService;
BeanOverrideTests(@MockitoBean CustomService customService) {
this.customService = customService;
}
// tests...
}
With Kotlin this can be achieved even more succinctly via a compact
constructor declaration:
@SpringJUnitConfig(TestConfig::class)
class BeanOverrideTests(@MockitoBean val customService: CustomService) {
// tests...
}
Of course, if one is a fan of so-called "test records", that can also
be achieved succinctly with a Java record:
@SpringJUnitConfig(TestConfig.class)
record BeanOverrideTests(@MockitoBean CustomService customService) {
// tests...
}
Closes gh-36096
This commit is contained in:
+1
-1
@@ -24,7 +24,7 @@ import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
|
||||
@Target({ElementType.FIELD, ElementType.METHOD})
|
||||
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
@Qualifier
|
||||
|
||||
+70
@@ -88,6 +88,65 @@ class MockitoBeanConfigurationErrorTests {
|
||||
List.of("bean1", "bean2"));
|
||||
}
|
||||
|
||||
@Test // gh-36096
|
||||
void cannotOverrideBeanByNameWithNoSuchBeanNameOnConstructorParameter() {
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
context.registerBean("anotherBean", String.class, () -> "example");
|
||||
BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(FailureByNameLookupOnConstructorParameter.class, context);
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(context::refresh)
|
||||
.withMessage("""
|
||||
Unable to replace bean: there is no bean with name 'beanToOverride' and type \
|
||||
java.lang.String (as required by parameter 'example' in constructor for %s). \
|
||||
If the bean is defined in a @Bean method, make sure the return type is the most \
|
||||
specific type possible (for example, the concrete implementation type).""",
|
||||
FailureByNameLookupOnConstructorParameter.class.getName());
|
||||
}
|
||||
|
||||
@Test // gh-36096
|
||||
void cannotOverrideBeanByNameWithBeanOfWrongTypeOnConstructorParameter() {
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
context.registerBean("beanToOverride", Integer.class, () -> 42);
|
||||
BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(FailureByNameLookupOnConstructorParameter.class, context);
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(context::refresh)
|
||||
.withMessage("""
|
||||
Unable to replace bean: there is no bean with name 'beanToOverride' and type \
|
||||
java.lang.String (as required by parameter 'example' in constructor for %s). \
|
||||
If the bean is defined in a @Bean method, make sure the return type is the most \
|
||||
specific type possible (for example, the concrete implementation type).""",
|
||||
FailureByNameLookupOnConstructorParameter.class.getName());
|
||||
}
|
||||
|
||||
@Test // gh-36096
|
||||
void cannotOverrideBeanByTypeWithNoSuchBeanTypeOnConstructorParameter() {
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(FailureByTypeLookupOnConstructorParameter.class, context);
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(context::refresh)
|
||||
.withMessage("""
|
||||
Unable to override bean: there are no beans of type java.lang.String \
|
||||
(as required by parameter 'example' in constructor for %s). \
|
||||
If the bean is defined in a @Bean method, make sure the return type is the most \
|
||||
specific type possible (for example, the concrete implementation type).""",
|
||||
FailureByTypeLookupOnConstructorParameter.class.getName());
|
||||
}
|
||||
|
||||
@Test // gh-36096
|
||||
void cannotOverrideBeanByTypeWithTooManyBeansOfThatTypeOnConstructorParameter() {
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
context.registerBean("bean1", String.class, () -> "example1");
|
||||
context.registerBean("bean2", String.class, () -> "example2");
|
||||
BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(FailureByTypeLookupOnConstructorParameter.class, context);
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(context::refresh)
|
||||
.withMessage("""
|
||||
Unable to select a bean to override: found 2 beans of type java.lang.String \
|
||||
(as required by parameter 'example' in constructor for %s): %s""",
|
||||
FailureByTypeLookupOnConstructorParameter.class.getName(),
|
||||
List.of("bean1", "bean2"));
|
||||
}
|
||||
|
||||
|
||||
static class FailureByTypeLookup {
|
||||
|
||||
@@ -99,7 +158,18 @@ class MockitoBeanConfigurationErrorTests {
|
||||
|
||||
@MockitoBean(name = "beanToOverride", enforceOverride = true)
|
||||
String example;
|
||||
}
|
||||
|
||||
static class FailureByTypeLookupOnConstructorParameter {
|
||||
|
||||
FailureByTypeLookupOnConstructorParameter(@MockitoBean(enforceOverride = true) String example) {
|
||||
}
|
||||
}
|
||||
|
||||
static class FailureByNameLookupOnConstructorParameter {
|
||||
|
||||
FailureByNameLookupOnConstructorParameter(@MockitoBean(name = "beanToOverride", enforceOverride = true) String example) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -194,7 +194,7 @@ class MockitoBeanOverrideHandlerTests {
|
||||
|
||||
private MockitoBeanOverrideHandler createHandler(Class<?> clazz) {
|
||||
MockitoBean annotation = AnnotatedElementUtils.getMergedAnnotation(clazz, MockitoBean.class);
|
||||
return new MockitoBeanOverrideHandler(null, ResolvableType.forClass(annotation.types()[0]), annotation);
|
||||
return new MockitoBeanOverrideHandler((Field) null, ResolvableType.forClass(annotation.types()[0]), annotation);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+76
@@ -18,6 +18,7 @@ package org.springframework.test.context.bean.override.mockito;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.List;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
@@ -102,6 +103,81 @@ class MockitoBeanOverrideProcessorTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Nested // gh-36096
|
||||
class CreateHandlerForParameterTests {
|
||||
|
||||
private final Parameter parameter = TestCase.class.getDeclaredConstructors()[0].getParameters()[0];
|
||||
|
||||
|
||||
@Test
|
||||
void mockAnnotationCreatesMockitoBeanOverrideHandler() {
|
||||
MockitoBean annotation = AnnotationUtils.synthesizeAnnotation(MockitoBean.class);
|
||||
BeanOverrideHandler handler = processor.createHandler(annotation, TestCase.class, parameter);
|
||||
|
||||
assertThat(handler).isExactlyInstanceOf(MockitoBeanOverrideHandler.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void spyAnnotationCreatesMockitoSpyBeanOverrideHandler() {
|
||||
MockitoSpyBean annotation = AnnotationUtils.synthesizeAnnotation(MockitoSpyBean.class);
|
||||
BeanOverrideHandler handler = processor.createHandler(annotation, TestCase.class, parameter);
|
||||
|
||||
assertThat(handler).isExactlyInstanceOf(MockitoSpyBeanOverrideHandler.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void otherAnnotationThrows() {
|
||||
Annotation annotation = parameter.getAnnotation(Nullable.class);
|
||||
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> processor.createHandler(annotation, TestCase.class, parameter))
|
||||
.withMessage("Invalid annotation passed to MockitoBeanOverrideProcessor: expected either " +
|
||||
"@MockitoBean or @MockitoSpyBean on parameter '%s' in constructor %s",
|
||||
parameter.getName(), parameter.getDeclaringExecutable().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void typesAttributeNotSupportedForMockitoBean() {
|
||||
Parameter parameter = TypesNotSupportedForMockitoBeanTestCase.class
|
||||
.getDeclaredConstructors()[0].getParameters()[0];
|
||||
MockitoBean annotation = parameter.getAnnotation(MockitoBean.class);
|
||||
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> processor.createHandler(annotation, TypesNotSupportedForMockitoBeanTestCase.class, parameter))
|
||||
.withMessage("The @MockitoBean 'types' attribute must be omitted when declared on a parameter");
|
||||
}
|
||||
|
||||
@Test
|
||||
void typesAttributeNotSupportedForMockitoSpyBean() {
|
||||
Parameter parameter = TypesNotSupportedForMockitoSpyBeanTestCase.class
|
||||
.getDeclaredConstructors()[0].getParameters()[0];
|
||||
MockitoSpyBean annotation = parameter.getAnnotation(MockitoSpyBean.class);
|
||||
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> processor.createHandler(annotation, TypesNotSupportedForMockitoSpyBeanTestCase.class, parameter))
|
||||
.withMessage("The @MockitoSpyBean 'types' attribute must be omitted when declared on a parameter");
|
||||
}
|
||||
|
||||
|
||||
static class TestCase {
|
||||
|
||||
TestCase(@MockitoBean @MockitoSpyBean @Nullable Integer number) {
|
||||
}
|
||||
}
|
||||
|
||||
static class TypesNotSupportedForMockitoBeanTestCase {
|
||||
|
||||
TypesNotSupportedForMockitoBeanTestCase(@MockitoBean(types = Integer.class) String param) {
|
||||
}
|
||||
}
|
||||
|
||||
static class TypesNotSupportedForMockitoSpyBeanTestCase {
|
||||
|
||||
TypesNotSupportedForMockitoSpyBeanTestCase(@MockitoSpyBean(types = Integer.class) String param) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class CreateHandlersTests {
|
||||
|
||||
|
||||
+55
@@ -113,6 +113,50 @@ class MockitoSpyBeanConfigurationErrorTests {
|
||||
to spy on a scoped proxy, which is not supported.""");
|
||||
}
|
||||
|
||||
@Test // gh-36096
|
||||
void contextCustomizerCannotBeCreatedWithNoSuchBeanNameOnConstructorParameter() {
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
context.registerBean("present", String.class, () -> "example");
|
||||
BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(ByNameSingleLookupOnConstructorParameter.class, context);
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(context::refresh)
|
||||
.withMessage("""
|
||||
Unable to wrap bean: there is no bean with name 'beanToSpy' and type \
|
||||
java.lang.String (as required by parameter 'example' in constructor for %s). \
|
||||
If the bean is defined in a @Bean method, make sure the return type is the most \
|
||||
specific type possible (for example, the concrete implementation type).""",
|
||||
ByNameSingleLookupOnConstructorParameter.class.getName());
|
||||
}
|
||||
|
||||
@Test // gh-36096
|
||||
void contextCustomizerCannotBeCreatedWithNoSuchBeanTypeOnConstructorParameter() {
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(ByTypeSingleLookupOnConstructorParameter.class, context);
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(context::refresh)
|
||||
.withMessage("""
|
||||
Unable to select a bean to wrap: there are no beans of type java.lang.String \
|
||||
(as required by parameter 'example' in constructor for %s). \
|
||||
If the bean is defined in a @Bean method, make sure the return type is the most \
|
||||
specific type possible (for example, the concrete implementation type).""",
|
||||
ByTypeSingleLookupOnConstructorParameter.class.getName());
|
||||
}
|
||||
|
||||
@Test // gh-36096
|
||||
void contextCustomizerCannotBeCreatedWithTooManyBeansOfThatTypeOnConstructorParameter() {
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
context.registerBean("bean1", String.class, () -> "example1");
|
||||
context.registerBean("bean2", String.class, () -> "example2");
|
||||
BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(ByTypeSingleLookupOnConstructorParameter.class, context);
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(context::refresh)
|
||||
.withMessage("""
|
||||
Unable to select a bean to wrap: found 2 beans of type java.lang.String \
|
||||
(as required by parameter 'example' in constructor for %s): %s""",
|
||||
ByTypeSingleLookupOnConstructorParameter.class.getName(),
|
||||
List.of("bean1", "bean2"));
|
||||
}
|
||||
|
||||
|
||||
static class ByTypeSingleLookup {
|
||||
|
||||
@@ -124,7 +168,18 @@ class MockitoSpyBeanConfigurationErrorTests {
|
||||
|
||||
@MockitoSpyBean("beanToSpy")
|
||||
String example;
|
||||
}
|
||||
|
||||
static class ByTypeSingleLookupOnConstructorParameter {
|
||||
|
||||
ByTypeSingleLookupOnConstructorParameter(@MockitoSpyBean String example) {
|
||||
}
|
||||
}
|
||||
|
||||
static class ByNameSingleLookupOnConstructorParameter {
|
||||
|
||||
ByNameSingleLookupOnConstructorParameter(@MockitoSpyBean("beanToSpy") String example) {
|
||||
}
|
||||
}
|
||||
|
||||
static class ScopedProxyTestCase {
|
||||
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyright 2002-present 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
|
||||
*
|
||||
* https://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.test.context.bean.override.mockito.constructor;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
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.test.context.bean.override.example.ExampleService;
|
||||
import org.springframework.test.context.bean.override.example.RealExampleService;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import org.springframework.test.mockito.MockitoAssertions;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link MockitoBean @MockitoBean} that use by-name lookup
|
||||
* on constructor parameters.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 7.1
|
||||
* @see <a href="https://github.com/spring-projects/spring-framework/issues/36096">gh-36096</a>
|
||||
* @see org.springframework.test.context.bean.override.mockito.MockitoBeanByNameLookupTestMethodScopedExtensionContextIntegrationTests
|
||||
*/
|
||||
@SpringJUnitConfig
|
||||
class MockitoBeanByNameLookupForConstructorParametersIntegrationTests {
|
||||
|
||||
final ExampleService service0A;
|
||||
|
||||
final ExampleService service0B;
|
||||
|
||||
final ExampleService service0C;
|
||||
|
||||
final ExampleService nonExisting;
|
||||
|
||||
|
||||
MockitoBeanByNameLookupForConstructorParametersIntegrationTests(
|
||||
@MockitoBean ExampleService s0A,
|
||||
@MockitoBean(name = "s0B") ExampleService service0B,
|
||||
@MockitoBean @Qualifier("s0C") ExampleService service0C,
|
||||
@MockitoBean("nonExistingBean") ExampleService nonExisting) {
|
||||
|
||||
this.service0A = s0A;
|
||||
this.service0B = service0B;
|
||||
this.service0C = service0C;
|
||||
this.nonExisting = nonExisting;
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void parameterNameIsUsedAsBeanName(ApplicationContext ctx) {
|
||||
assertThat(this.service0A)
|
||||
.satisfies(MockitoAssertions::assertIsMock)
|
||||
.isSameAs(ctx.getBean("s0A"));
|
||||
|
||||
assertThat(this.service0A.greeting()).as("mocked greeting").isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void explicitBeanNameOverridesParameterName(ApplicationContext ctx) {
|
||||
assertThat(this.service0B)
|
||||
.satisfies(MockitoAssertions::assertIsMock)
|
||||
.isSameAs(ctx.getBean("s0B"));
|
||||
|
||||
assertThat(this.service0B.greeting()).as("mocked greeting").isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void qualifierIsUsedToResolveByName(ApplicationContext ctx) {
|
||||
assertThat(this.service0C)
|
||||
.satisfies(MockitoAssertions::assertIsMock)
|
||||
.isSameAs(ctx.getBean("s0C"));
|
||||
|
||||
assertThat(this.service0C.greeting()).as("mocked greeting").isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void mockIsCreatedWhenNoBeanExistsWithProvidedName(ApplicationContext ctx) {
|
||||
assertThat(this.nonExisting)
|
||||
.satisfies(MockitoAssertions::assertIsMock)
|
||||
.isSameAs(ctx.getBean("nonExistingBean"));
|
||||
|
||||
assertThat(this.nonExisting.greeting()).as("mocked greeting").isNull();
|
||||
}
|
||||
|
||||
|
||||
@Nested
|
||||
class NestedTests {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("s0A")
|
||||
ExampleService localService0A;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("nonExistingBean")
|
||||
ExampleService localNonExisting;
|
||||
|
||||
final ExampleService nestedNonExisting;
|
||||
|
||||
|
||||
NestedTests(@MockitoBean("nestedNonExistingBean") ExampleService nestedNonExisting) {
|
||||
this.nestedNonExisting = nestedNonExisting;
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void mockFromEnclosingClassIsAccessibleViaAutowiring(ApplicationContext ctx) {
|
||||
assertThat(this.localService0A)
|
||||
.satisfies(MockitoAssertions::assertIsMock)
|
||||
.isSameAs(service0A)
|
||||
.isSameAs(ctx.getBean("s0A"));
|
||||
|
||||
assertThat(this.localService0A.greeting()).as("mocked greeting").isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void mockForNonExistingBeanFromEnclosingClassIsAccessibleViaAutowiring(ApplicationContext ctx) {
|
||||
assertThat(this.localNonExisting)
|
||||
.satisfies(MockitoAssertions::assertIsMock)
|
||||
.isSameAs(nonExisting)
|
||||
.isSameAs(ctx.getBean("nonExistingBean"));
|
||||
|
||||
assertThat(this.localNonExisting.greeting()).as("mocked greeting").isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedConstructorParameterIsMockedWhenNoBeanExistsWithProvidedName(ApplicationContext ctx) {
|
||||
assertThat(this.nestedNonExisting)
|
||||
.satisfies(MockitoAssertions::assertIsMock)
|
||||
.isSameAs(ctx.getBean("nestedNonExistingBean"));
|
||||
|
||||
assertThat(this.nestedNonExisting.greeting()).as("mocked greeting").isNull();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
ExampleService s0A() {
|
||||
return new RealExampleService("prod s0A");
|
||||
}
|
||||
|
||||
@Bean
|
||||
ExampleService s0B() {
|
||||
return new RealExampleService("prod s0B");
|
||||
}
|
||||
|
||||
@Bean
|
||||
ExampleService s0C() {
|
||||
return new RealExampleService("prod s0C");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2002-present 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
|
||||
*
|
||||
* https://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.test.context.bean.override.mockito.constructor;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.test.context.bean.override.example.ExampleService;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.mockito.MockitoAssertions.assertIsMock;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link MockitoBean @MockitoBean} that use by-type lookup
|
||||
* on constructor parameters in a Java record.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 7.1
|
||||
* @see <a href="https://github.com/spring-projects/spring-framework/issues/36096">gh-36096</a>
|
||||
*/
|
||||
@SpringJUnitConfig
|
||||
record MockitoBeanByTypeLookupForConstructorParametersIntegrationRecordTests(
|
||||
@MockitoBean ExampleService exampleService) {
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
assertIsMock(this.exampleService);
|
||||
|
||||
when(this.exampleService.greeting()).thenReturn("Mocked greeting");
|
||||
|
||||
assertThat(this.exampleService.greeting()).isEqualTo("Mocked greeting");
|
||||
verify(this.exampleService, times(1)).greeting();
|
||||
verifyNoMoreInteractions(this.exampleService);
|
||||
}
|
||||
|
||||
}
|
||||
+207
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
* Copyright 2002-present 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
|
||||
*
|
||||
* https://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.test.context.bean.override.mockito.constructor;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
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.core.annotation.Order;
|
||||
import org.springframework.test.context.bean.override.example.CustomQualifier;
|
||||
import org.springframework.test.context.bean.override.example.ExampleService;
|
||||
import org.springframework.test.context.bean.override.example.RealExampleService;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import org.springframework.test.mockito.MockitoAssertions;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.mockito.MockitoAssertions.assertIsMock;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link MockitoBean @MockitoBean} that use by-type lookup
|
||||
* on constructor parameters.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 7.1
|
||||
* @see <a href="https://github.com/spring-projects/spring-framework/issues/36096">gh-36096</a>
|
||||
* @see org.springframework.test.context.bean.override.mockito.MockitoBeanByTypeLookupIntegrationTests
|
||||
*/
|
||||
@SpringJUnitConfig
|
||||
class MockitoBeanByTypeLookupForConstructorParametersIntegrationTests {
|
||||
|
||||
final AnotherService serviceIsNotABean;
|
||||
|
||||
final ExampleService anyNameForService;
|
||||
|
||||
final StringBuilder ambiguous;
|
||||
|
||||
final StringBuilder ambiguousMeta;
|
||||
|
||||
|
||||
MockitoBeanByTypeLookupForConstructorParametersIntegrationTests(
|
||||
@MockitoBean AnotherService serviceIsNotABean,
|
||||
@MockitoBean ExampleService anyNameForService,
|
||||
@MockitoBean @Qualifier("prefer") StringBuilder ambiguous,
|
||||
@MockitoBean @CustomQualifier StringBuilder ambiguousMeta) {
|
||||
|
||||
this.serviceIsNotABean = serviceIsNotABean;
|
||||
this.anyNameForService = anyNameForService;
|
||||
this.ambiguous = ambiguous;
|
||||
this.ambiguousMeta = ambiguousMeta;
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void mockIsCreatedWhenNoCandidateIsFound() {
|
||||
assertIsMock(this.serviceIsNotABean);
|
||||
|
||||
when(this.serviceIsNotABean.hello()).thenReturn("Mocked hello");
|
||||
|
||||
assertThat(this.serviceIsNotABean.hello()).isEqualTo("Mocked hello");
|
||||
verify(this.serviceIsNotABean, times(1)).hello();
|
||||
verifyNoMoreInteractions(this.serviceIsNotABean);
|
||||
}
|
||||
|
||||
@Test
|
||||
void overrideIsFoundByType(ApplicationContext ctx) {
|
||||
assertThat(this.anyNameForService)
|
||||
.satisfies(MockitoAssertions::assertIsMock)
|
||||
.isSameAs(ctx.getBean("example"))
|
||||
.isSameAs(ctx.getBean(ExampleService.class));
|
||||
|
||||
when(this.anyNameForService.greeting()).thenReturn("Mocked greeting");
|
||||
|
||||
assertThat(this.anyNameForService.greeting()).isEqualTo("Mocked greeting");
|
||||
verify(this.anyNameForService, times(1)).greeting();
|
||||
verifyNoMoreInteractions(this.anyNameForService);
|
||||
}
|
||||
|
||||
@Test
|
||||
void overrideIsFoundByTypeAndDisambiguatedByQualifier(ApplicationContext ctx) {
|
||||
assertThat(this.ambiguous)
|
||||
.satisfies(MockitoAssertions::assertIsMock)
|
||||
.isSameAs(ctx.getBean("ambiguous2"));
|
||||
|
||||
assertThatExceptionOfType(NoUniqueBeanDefinitionException.class)
|
||||
.isThrownBy(() -> ctx.getBean(StringBuilder.class))
|
||||
.satisfies(ex -> assertThat(ex.getBeanNamesFound()).containsOnly("ambiguous1", "ambiguous2"));
|
||||
|
||||
assertThat(this.ambiguous).isEmpty();
|
||||
assertThat(this.ambiguous.substring(0)).isNull();
|
||||
verify(this.ambiguous, times(1)).length();
|
||||
verify(this.ambiguous, times(1)).substring(anyInt());
|
||||
verifyNoMoreInteractions(this.ambiguous);
|
||||
}
|
||||
|
||||
@Test
|
||||
void overrideIsFoundByTypeAndDisambiguatedByMetaQualifier(ApplicationContext ctx) {
|
||||
assertThat(this.ambiguousMeta)
|
||||
.satisfies(MockitoAssertions::assertIsMock)
|
||||
.isSameAs(ctx.getBean("ambiguous1"));
|
||||
|
||||
assertThatExceptionOfType(NoUniqueBeanDefinitionException.class)
|
||||
.isThrownBy(() -> ctx.getBean(StringBuilder.class))
|
||||
.satisfies(ex -> assertThat(ex.getBeanNamesFound()).containsOnly("ambiguous1", "ambiguous2"));
|
||||
|
||||
assertThat(this.ambiguousMeta).isEmpty();
|
||||
assertThat(this.ambiguousMeta.substring(0)).isNull();
|
||||
verify(this.ambiguousMeta, times(1)).length();
|
||||
verify(this.ambiguousMeta, times(1)).substring(anyInt());
|
||||
verifyNoMoreInteractions(this.ambiguousMeta);
|
||||
}
|
||||
|
||||
|
||||
@Nested
|
||||
class NestedTests {
|
||||
|
||||
@Autowired
|
||||
ExampleService localAnyNameForService;
|
||||
|
||||
final NestedService nestedService;
|
||||
|
||||
|
||||
NestedTests(@MockitoBean NestedService nestedService) {
|
||||
this.nestedService = nestedService;
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void mockFromEnclosingClassConstructorParameterIsAccessibleViaAutowiring(ApplicationContext ctx) {
|
||||
assertThat(this.localAnyNameForService)
|
||||
.satisfies(MockitoAssertions::assertIsMock)
|
||||
.isSameAs(anyNameForService)
|
||||
.isSameAs(ctx.getBean("example"))
|
||||
.isSameAs(ctx.getBean(ExampleService.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedConstructorParameterIsAMock() {
|
||||
assertIsMock(this.nestedService);
|
||||
|
||||
when(this.nestedService.hello()).thenReturn("Nested hello");
|
||||
assertThat(this.nestedService.hello()).isEqualTo("Nested hello");
|
||||
verify(this.nestedService).hello();
|
||||
verifyNoMoreInteractions(this.nestedService);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public interface AnotherService {
|
||||
|
||||
String hello();
|
||||
}
|
||||
|
||||
public interface NestedService {
|
||||
|
||||
String hello();
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class Config {
|
||||
|
||||
@Bean("example")
|
||||
ExampleService bean1() {
|
||||
return new RealExampleService("Production hello");
|
||||
}
|
||||
|
||||
@Bean("ambiguous1")
|
||||
@Order(1)
|
||||
@CustomQualifier
|
||||
StringBuilder bean2() {
|
||||
return new StringBuilder("bean2");
|
||||
}
|
||||
|
||||
@Bean("ambiguous2")
|
||||
@Order(2)
|
||||
@Qualifier("prefer")
|
||||
StringBuilder bean3() {
|
||||
return new StringBuilder("bean3");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Copyright 2002-present 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
|
||||
*
|
||||
* https://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.test.context.bean.override.mockito.constructor;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
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.test.context.bean.override.example.ExampleService;
|
||||
import org.springframework.test.context.bean.override.example.RealExampleService;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import org.springframework.test.mockito.MockitoAssertions;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link MockitoSpyBean @MockitoSpyBean} that use by-name
|
||||
* lookup on constructor parameters.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 7.1
|
||||
* @see <a href="https://github.com/spring-projects/spring-framework/issues/36096">gh-36096</a>
|
||||
* @see org.springframework.test.context.bean.override.mockito.MockitoSpyBeanByNameLookupTestMethodScopedExtensionContextIntegrationTests
|
||||
*/
|
||||
@SpringJUnitConfig
|
||||
class MockitoSpyBeanByNameLookupForConstructorParametersIntegrationTests {
|
||||
|
||||
final ExampleService service1;
|
||||
|
||||
final ExampleService service2;
|
||||
|
||||
final ExampleService service3;
|
||||
|
||||
|
||||
MockitoSpyBeanByNameLookupForConstructorParametersIntegrationTests(
|
||||
@MockitoSpyBean ExampleService s1,
|
||||
@MockitoSpyBean("s2") ExampleService service2,
|
||||
@MockitoSpyBean @Qualifier("s3") ExampleService service3) {
|
||||
|
||||
this.service1 = s1;
|
||||
this.service2 = service2;
|
||||
this.service3 = service3;
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void parameterNameIsUsedAsBeanName(ApplicationContext ctx) {
|
||||
assertThat(this.service1)
|
||||
.satisfies(MockitoAssertions::assertIsSpy)
|
||||
.isSameAs(ctx.getBean("s1"));
|
||||
|
||||
assertThat(this.service1.greeting()).isEqualTo("prod 1");
|
||||
verify(this.service1).greeting();
|
||||
verifyNoMoreInteractions(this.service1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void explicitBeanNameOverridesParameterName(ApplicationContext ctx) {
|
||||
assertThat(this.service2)
|
||||
.satisfies(MockitoAssertions::assertIsSpy)
|
||||
.isSameAs(ctx.getBean("s2"));
|
||||
|
||||
assertThat(this.service2.greeting()).isEqualTo("prod 2");
|
||||
verify(this.service2).greeting();
|
||||
verifyNoMoreInteractions(this.service2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void qualifierIsUsedToResolveByName(ApplicationContext ctx) {
|
||||
assertThat(this.service3)
|
||||
.satisfies(MockitoAssertions::assertIsSpy)
|
||||
.isSameAs(ctx.getBean("s3"));
|
||||
|
||||
assertThat(this.service3.greeting()).isEqualTo("prod 3");
|
||||
verify(this.service3).greeting();
|
||||
verifyNoMoreInteractions(this.service3);
|
||||
}
|
||||
|
||||
|
||||
@Nested
|
||||
class NestedTests {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("s1")
|
||||
ExampleService localService1;
|
||||
|
||||
final ExampleService nestedSpy;
|
||||
|
||||
|
||||
NestedTests(@MockitoSpyBean("s4") ExampleService nestedSpy) {
|
||||
this.nestedSpy = nestedSpy;
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void spyFromEnclosingClassIsAccessibleViaAutowiring(ApplicationContext ctx) {
|
||||
assertThat(this.localService1)
|
||||
.satisfies(MockitoAssertions::assertIsSpy)
|
||||
.isSameAs(service1)
|
||||
.isSameAs(ctx.getBean("s1"));
|
||||
|
||||
assertThat(this.localService1.greeting()).isEqualTo("prod 1");
|
||||
verify(this.localService1).greeting();
|
||||
verifyNoMoreInteractions(this.localService1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedConstructorParameterIsASpy(ApplicationContext ctx) {
|
||||
assertThat(this.nestedSpy)
|
||||
.satisfies(MockitoAssertions::assertIsSpy)
|
||||
.isSameAs(ctx.getBean("s4"));
|
||||
|
||||
assertThat(this.nestedSpy.greeting()).isEqualTo("prod 4");
|
||||
verify(this.nestedSpy).greeting();
|
||||
verifyNoMoreInteractions(this.nestedSpy);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
ExampleService s1() {
|
||||
return new RealExampleService("prod 1");
|
||||
}
|
||||
|
||||
@Bean
|
||||
ExampleService s2() {
|
||||
return new RealExampleService("prod 2");
|
||||
}
|
||||
|
||||
@Bean
|
||||
ExampleService s3() {
|
||||
return new RealExampleService("prod 3");
|
||||
}
|
||||
|
||||
@Bean
|
||||
ExampleService s4() {
|
||||
return new RealExampleService("prod 4");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+213
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* Copyright 2002-present 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
|
||||
*
|
||||
* https://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.test.context.bean.override.mockito.constructor;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
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.core.annotation.Order;
|
||||
import org.springframework.test.context.bean.override.example.CustomQualifier;
|
||||
import org.springframework.test.context.bean.override.example.ExampleService;
|
||||
import org.springframework.test.context.bean.override.example.RealExampleService;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import org.springframework.test.mockito.MockitoAssertions;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatException;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link MockitoSpyBean @MockitoSpyBean} that use by-type
|
||||
* lookup on constructor parameters.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 7.1
|
||||
* @see <a href="https://github.com/spring-projects/spring-framework/issues/36096">gh-36096</a>
|
||||
* @see org.springframework.test.context.bean.override.mockito.MockitoSpyBeanByTypeLookupIntegrationTests
|
||||
*/
|
||||
@SpringJUnitConfig
|
||||
class MockitoSpyBeanByTypeLookupForConstructorParametersIntegrationTests {
|
||||
|
||||
final ExampleService anyNameForService;
|
||||
|
||||
final StringHolder ambiguous;
|
||||
|
||||
final StringHolder ambiguousMeta;
|
||||
|
||||
|
||||
MockitoSpyBeanByTypeLookupForConstructorParametersIntegrationTests(
|
||||
@MockitoSpyBean ExampleService anyNameForService,
|
||||
@MockitoSpyBean @Qualifier("prefer") StringHolder ambiguous,
|
||||
@MockitoSpyBean @CustomQualifier StringHolder ambiguousMeta) {
|
||||
|
||||
this.anyNameForService = anyNameForService;
|
||||
this.ambiguous = ambiguous;
|
||||
this.ambiguousMeta = ambiguousMeta;
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void overrideIsFoundByType(ApplicationContext ctx) {
|
||||
assertThat(this.anyNameForService)
|
||||
.satisfies(MockitoAssertions::assertIsSpy)
|
||||
.isSameAs(ctx.getBean("example"))
|
||||
.isSameAs(ctx.getBean(ExampleService.class));
|
||||
|
||||
assertThat(this.anyNameForService.greeting()).isEqualTo("Production hello");
|
||||
verify(this.anyNameForService).greeting();
|
||||
verifyNoMoreInteractions(this.anyNameForService);
|
||||
}
|
||||
|
||||
@Test
|
||||
void overrideIsFoundByTypeAndDisambiguatedByQualifier(ApplicationContext ctx) {
|
||||
assertThat(this.ambiguous)
|
||||
.satisfies(MockitoAssertions::assertIsSpy)
|
||||
.isSameAs(ctx.getBean("ambiguous2"));
|
||||
|
||||
assertThatException()
|
||||
.isThrownBy(() -> ctx.getBean(StringHolder.class))
|
||||
.withMessageEndingWith("but found 2: ambiguous1,ambiguous2");
|
||||
|
||||
assertThat(this.ambiguous.getValue()).isEqualTo("bean3");
|
||||
assertThat(this.ambiguous.size()).isEqualTo(5);
|
||||
verify(this.ambiguous).getValue();
|
||||
verify(this.ambiguous).size();
|
||||
verifyNoMoreInteractions(this.ambiguous);
|
||||
}
|
||||
|
||||
@Test
|
||||
void overrideIsFoundByTypeAndDisambiguatedByMetaQualifier(ApplicationContext ctx) {
|
||||
assertThat(this.ambiguousMeta)
|
||||
.satisfies(MockitoAssertions::assertIsSpy)
|
||||
.isSameAs(ctx.getBean("ambiguous1"));
|
||||
|
||||
assertThatException()
|
||||
.isThrownBy(() -> ctx.getBean(StringHolder.class))
|
||||
.withMessageEndingWith("but found 2: ambiguous1,ambiguous2");
|
||||
|
||||
assertThat(this.ambiguousMeta.getValue()).isEqualTo("bean2");
|
||||
assertThat(this.ambiguousMeta.size()).isEqualTo(5);
|
||||
verify(this.ambiguousMeta).getValue();
|
||||
verify(this.ambiguousMeta).size();
|
||||
verifyNoMoreInteractions(this.ambiguousMeta);
|
||||
}
|
||||
|
||||
|
||||
@Nested
|
||||
class NestedTests {
|
||||
|
||||
@Autowired
|
||||
ExampleService localAnyNameForService;
|
||||
|
||||
final AnotherService nestedSpy;
|
||||
|
||||
|
||||
NestedTests(@MockitoSpyBean AnotherService nestedSpy) {
|
||||
this.nestedSpy = nestedSpy;
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void spyFromEnclosingClassConstructorParameterIsAccessibleViaAutowiring(ApplicationContext ctx) {
|
||||
assertThat(this.localAnyNameForService)
|
||||
.satisfies(MockitoAssertions::assertIsSpy)
|
||||
.isSameAs(anyNameForService)
|
||||
.isSameAs(ctx.getBean("example"))
|
||||
.isSameAs(ctx.getBean(ExampleService.class));
|
||||
|
||||
assertThat(this.localAnyNameForService.greeting()).isEqualTo("Production hello");
|
||||
verify(this.localAnyNameForService).greeting();
|
||||
verifyNoMoreInteractions(this.localAnyNameForService);
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedConstructorParameterIsASpy(ApplicationContext ctx) {
|
||||
assertThat(this.nestedSpy)
|
||||
.satisfies(MockitoAssertions::assertIsSpy)
|
||||
.isSameAs(ctx.getBean("anotherService"))
|
||||
.isSameAs(ctx.getBean(AnotherService.class));
|
||||
|
||||
assertThat(this.nestedSpy.hello()).isEqualTo("Another hello");
|
||||
verify(this.nestedSpy).hello();
|
||||
verifyNoMoreInteractions(this.nestedSpy);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
interface AnotherService {
|
||||
|
||||
String hello();
|
||||
}
|
||||
|
||||
static class StringHolder {
|
||||
|
||||
private final String value;
|
||||
|
||||
StringHolder(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return this.value.length();
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class Config {
|
||||
|
||||
@Bean("example")
|
||||
ExampleService bean1() {
|
||||
return new RealExampleService("Production hello");
|
||||
}
|
||||
|
||||
@Bean("ambiguous1")
|
||||
@Order(1)
|
||||
@CustomQualifier
|
||||
StringHolder bean2() {
|
||||
return new StringHolder("bean2");
|
||||
}
|
||||
|
||||
@Bean("ambiguous2")
|
||||
@Order(2)
|
||||
@Qualifier("prefer")
|
||||
StringHolder bean3() {
|
||||
return new StringHolder("bean3");
|
||||
}
|
||||
|
||||
@Bean
|
||||
AnotherService anotherService() {
|
||||
return new AnotherService() {
|
||||
@Override
|
||||
public String hello() {
|
||||
return "Another hello";
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2002-present 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
|
||||
*
|
||||
* https://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.test.context.bean.override.mockito.typelevel;
|
||||
|
||||
interface ConstructorService01 extends Service {
|
||||
}
|
||||
+29
@@ -48,6 +48,10 @@ import static org.springframework.test.mockito.MockitoAssertions.assertIsNotMock
|
||||
@MockitoBean(name = "s2", types = ExampleService.class)
|
||||
class MockitoBeansByNameIntegrationTests {
|
||||
|
||||
final ExampleService service0A;
|
||||
final ExampleService service0B;
|
||||
final ExampleService service0C;
|
||||
|
||||
@Autowired
|
||||
ExampleService s1;
|
||||
|
||||
@@ -62,6 +66,16 @@ class MockitoBeansByNameIntegrationTests {
|
||||
ExampleService service4;
|
||||
|
||||
|
||||
MockitoBeansByNameIntegrationTests(@MockitoBean ExampleService s0A,
|
||||
@MockitoBean(name = "s0B") ExampleService service0B,
|
||||
@MockitoBean @Qualifier("s0C") ExampleService service0C) {
|
||||
|
||||
this.service0A = s0A;
|
||||
this.service0B = service0B;
|
||||
this.service0C = service0C;
|
||||
}
|
||||
|
||||
|
||||
@BeforeEach
|
||||
void configureMocks() {
|
||||
assertIsMock(s1, "s1");
|
||||
@@ -86,6 +100,21 @@ class MockitoBeansByNameIntegrationTests {
|
||||
@Configuration
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
ExampleService s0A() {
|
||||
return () -> "prod 0A";
|
||||
}
|
||||
|
||||
@Bean
|
||||
ExampleService s0B() {
|
||||
return () -> "prod 0B";
|
||||
}
|
||||
|
||||
@Bean
|
||||
ExampleService s0C() {
|
||||
return () -> "prod 0C";
|
||||
}
|
||||
|
||||
@Bean
|
||||
ExampleService s1() {
|
||||
return () -> "prod 1";
|
||||
|
||||
+12
@@ -63,10 +63,17 @@ class MockitoBeansByTypeIntegrationTests implements MockTestInterface01 {
|
||||
@Autowired
|
||||
Service06 service06;
|
||||
|
||||
final ConstructorService01 constructorService01;
|
||||
|
||||
@MockitoBean
|
||||
Service07 service07;
|
||||
|
||||
|
||||
MockitoBeansByTypeIntegrationTests(@MockitoBean ConstructorService01 constructorService01) {
|
||||
this.constructorService01 = constructorService01;
|
||||
}
|
||||
|
||||
|
||||
@BeforeEach
|
||||
void configureMocks() {
|
||||
assertIsMock(service01, "service01");
|
||||
@@ -75,6 +82,7 @@ class MockitoBeansByTypeIntegrationTests implements MockTestInterface01 {
|
||||
assertIsMock(service04, "service04");
|
||||
assertIsMock(service05, "service05");
|
||||
assertIsMock(service06, "service06");
|
||||
assertIsMock(constructorService01, "constructorService01");
|
||||
assertIsMock(service07, "service07");
|
||||
|
||||
given(service01.greeting()).willReturn("mock 01");
|
||||
@@ -83,6 +91,7 @@ class MockitoBeansByTypeIntegrationTests implements MockTestInterface01 {
|
||||
given(service04.greeting()).willReturn("mock 04");
|
||||
given(service05.greeting()).willReturn("mock 05");
|
||||
given(service06.greeting()).willReturn("mock 06");
|
||||
given(constructorService01.greeting()).willReturn("mock constructor 01");
|
||||
given(service07.greeting()).willReturn("mock 07");
|
||||
}
|
||||
|
||||
@@ -94,6 +103,7 @@ class MockitoBeansByTypeIntegrationTests implements MockTestInterface01 {
|
||||
assertThat(service04.greeting()).isEqualTo("mock 04");
|
||||
assertThat(service05.greeting()).isEqualTo("mock 05");
|
||||
assertThat(service06.greeting()).isEqualTo("mock 06");
|
||||
assertThat(constructorService01.greeting()).isEqualTo("mock constructor 01");
|
||||
assertThat(service07.greeting()).isEqualTo("mock 07");
|
||||
}
|
||||
|
||||
@@ -133,6 +143,7 @@ class MockitoBeansByTypeIntegrationTests implements MockTestInterface01 {
|
||||
assertIsMock(service04, "service04");
|
||||
assertIsMock(service05, "service05");
|
||||
assertIsMock(service06, "service06");
|
||||
assertIsMock(constructorService01, "constructorService01");
|
||||
assertIsMock(service07, "service07");
|
||||
assertIsMock(service08, "service08");
|
||||
assertIsMock(service09, "service09");
|
||||
@@ -157,6 +168,7 @@ class MockitoBeansByTypeIntegrationTests implements MockTestInterface01 {
|
||||
assertThat(service04.greeting()).isEqualTo("mock 04");
|
||||
assertThat(service05.greeting()).isEqualTo("mock 05");
|
||||
assertThat(service06.greeting()).isEqualTo("mock 06");
|
||||
assertThat(constructorService01.greeting()).isEqualTo("mock constructor 01");
|
||||
assertThat(service07.greeting()).isEqualTo("mock 07");
|
||||
assertThat(service08.greeting()).isEqualTo("mock 08");
|
||||
assertThat(service09.greeting()).isEqualTo("mock 09");
|
||||
|
||||
+3
-3
@@ -44,7 +44,7 @@ class MockitoBeansTests {
|
||||
Stream<Class<?>> mockedServices = getRegisteredMockTypes(MockitoBeansByTypeIntegrationTests.class);
|
||||
assertThat(mockedServices).containsExactly(
|
||||
Service01.class, Service02.class, Service03.class, Service04.class,
|
||||
Service05.class, Service06.class, Service07.class);
|
||||
Service05.class, Service06.class, ConstructorService01.class, Service07.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -52,8 +52,8 @@ class MockitoBeansTests {
|
||||
Stream<Class<?>> mockedServices = getRegisteredMockTypes(MockitoBeansByTypeIntegrationTests.NestedTests.class);
|
||||
assertThat(mockedServices).containsExactly(
|
||||
Service01.class, Service02.class, Service03.class, Service04.class,
|
||||
Service05.class, Service06.class, Service07.class, Service08.class,
|
||||
Service09.class, Service10.class, Service11.class, Service12.class,
|
||||
Service05.class, Service06.class, ConstructorService01.class, Service07.class,
|
||||
Service08.class, Service09.class, Service10.class, Service11.class, Service12.class,
|
||||
Service13.class);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user