From 0221471265d3ea50beeb23a9aa5f49096afb5380 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Thu, 24 Oct 2024 15:45:28 +0200 Subject: [PATCH] =?UTF-8?q?Verify=20@=E2=81=A0Mockito[Spy]Bean=20can=20moc?= =?UTF-8?q?k/spy=20parameterized=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See gh-33742 --- .../example/ExampleGenericService.java | 30 ++++++++ .../example/ExampleGenericServiceCaller.java | 34 +++++++++ .../example/IntegerExampleGenericService.java | 33 +++++++++ .../example/StringExampleGenericService.java | 27 +++++++ ...OnTestFieldForNewBeanIntegrationTests.java | 67 +++++++++++++++++ ...orExistingGenericBeanIntegrationTests.java | 74 +++++++++++++++++++ 6 files changed, 265 insertions(+) create mode 100644 spring-test/src/test/java/org/springframework/test/context/bean/override/example/ExampleGenericService.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/bean/override/example/ExampleGenericServiceCaller.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/bean/override/example/IntegerExampleGenericService.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/bean/override/example/StringExampleGenericService.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/integration/MockitoBeanWithGenericsOnTestFieldForNewBeanIntegrationTests.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/integration/MockitoSpyBeanWithGenericsOnTestFieldForExistingGenericBeanIntegrationTests.java diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/example/ExampleGenericService.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/example/ExampleGenericService.java new file mode 100644 index 00000000000..d81f9dfac82 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/example/ExampleGenericService.java @@ -0,0 +1,30 @@ +/* + * Copyright 2002-2024 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.example; + +/** + * Example generic service interface for tests. + * + * @param the generic type + * @author Phillip Webb + * @since 6.2 + */ +public interface ExampleGenericService { + + T greeting(); + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/example/ExampleGenericServiceCaller.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/example/ExampleGenericServiceCaller.java new file mode 100644 index 00000000000..f91bfb0a32b --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/example/ExampleGenericServiceCaller.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2024 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.example; + +/** + * Example bean that has dependencies on parameterized {@link ExampleGenericService} + * collaborators. + * + * @author Sam Brannen + * @author Phillip Webb + * @since 6.2 + */ +public record ExampleGenericServiceCaller(ExampleGenericService integerService, + ExampleGenericService stringService) { + + public String sayGreeting() { + return "I say " + this.stringService.greeting() + " " + this.integerService.greeting(); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/example/IntegerExampleGenericService.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/example/IntegerExampleGenericService.java new file mode 100644 index 00000000000..fd9a7095c48 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/example/IntegerExampleGenericService.java @@ -0,0 +1,33 @@ +/* + * Copyright 2002-2024 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.example; + +/** + * {@link ExampleGenericService} implementation for tests. + * + * @author Phillip Webb + * @author Sam Brannen + * @since 6.2 + */ +public class IntegerExampleGenericService implements ExampleGenericService { + + @Override + public Integer greeting() { + return 123; + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/example/StringExampleGenericService.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/example/StringExampleGenericService.java new file mode 100644 index 00000000000..40487b3f387 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/example/StringExampleGenericService.java @@ -0,0 +1,27 @@ +/* + * Copyright 2002-2024 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.example; + +/** + * {@link ExampleGenericService} implementation for tests. + * + * @author Sam Brannen + * @author Phillip Webb + * @since 6.2 + */ +public record StringExampleGenericService(String greeting) implements ExampleGenericService { +} diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/integration/MockitoBeanWithGenericsOnTestFieldForNewBeanIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/integration/MockitoBeanWithGenericsOnTestFieldForNewBeanIntegrationTests.java new file mode 100644 index 00000000000..eae4a87b7f9 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/integration/MockitoBeanWithGenericsOnTestFieldForNewBeanIntegrationTests.java @@ -0,0 +1,67 @@ +/* + * Copyright 2002-2024 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.integration; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.example.ExampleGenericService; +import org.springframework.test.context.bean.override.example.ExampleGenericServiceCaller; +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.BDDMockito.given; + +/** + * Tests that {@link MockitoBean @MockitoBean} on fields with generics can be used + * to inject new mock instances. + * + * @author Phillip Webb + * @author Sam Brannen + * @since 6.2 + * @see MockitoSpyBeanWithGenericsOnTestFieldForExistingGenericBeanIntegrationTests + */ +@SpringJUnitConfig +class MockitoBeanWithGenericsOnTestFieldForNewBeanIntegrationTests { + + @MockitoBean + ExampleGenericService stringService; + + @MockitoBean + ExampleGenericService integerService; + + @Autowired + ExampleGenericServiceCaller caller; + + + @Test + void testMocking() { + given(stringService.greeting()).willReturn("Hello"); + given(integerService.greeting()).willReturn(42); + assertThat(caller.sayGreeting()).isEqualTo("I say Hello 42"); + } + + + @Configuration(proxyBeanMethods = false) + @Import(ExampleGenericServiceCaller.class) + static class Config { + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/integration/MockitoSpyBeanWithGenericsOnTestFieldForExistingGenericBeanIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/integration/MockitoSpyBeanWithGenericsOnTestFieldForExistingGenericBeanIntegrationTests.java new file mode 100644 index 00000000000..b5d694977ac --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/integration/MockitoSpyBeanWithGenericsOnTestFieldForExistingGenericBeanIntegrationTests.java @@ -0,0 +1,74 @@ +/* + * Copyright 2002-2024 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.integration; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.example.ExampleGenericService; +import org.springframework.test.context.bean.override.example.ExampleGenericServiceCaller; +import org.springframework.test.context.bean.override.example.IntegerExampleGenericService; +import org.springframework.test.context.bean.override.example.StringExampleGenericService; +import org.springframework.test.context.bean.override.mockito.MockitoSpyBean; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.then; + +/** + * Tests that {@link MockitoSpyBean @MockitoSpyBean} on a field with generics can + * be used to replace an existing bean with matching generics. + * + * @author Phillip Webb + * @author Sam Brannen + * @since 6.2 + * @see MockitoBeanWithGenericsOnTestFieldForNewBeanIntegrationTests + */ +@SpringJUnitConfig +class MockitoSpyBeanWithGenericsOnTestFieldForExistingGenericBeanIntegrationTests { + + @MockitoSpyBean + ExampleGenericService service; + + @Autowired + ExampleGenericServiceCaller caller; + + + @Test + void testSpying() { + assertThat(caller.sayGreeting()).isEqualTo("I say Enigma 123"); + then(service).should().greeting(); + } + + + @Configuration(proxyBeanMethods = false) + @Import({ ExampleGenericServiceCaller.class, IntegerExampleGenericService.class }) + static class SpyBeanOnTestFieldForExistingBeanConfig { + + @Bean + ExampleGenericService simpleExampleStringGenericService() { + // In order to trigger the issue, we need a method signature that returns the + // generic type instead of the actual implementation class. + return new StringExampleGenericService("Enigma"); + } + + } + +}