Compare commits

..

1 Commits

Author SHA1 Message Date
Brian Clozel b4f10d4f1d Release v6.2.1 2024-12-12 10:18:03 +01:00
169 changed files with 1084 additions and 3370 deletions
@@ -31,11 +31,10 @@ runs:
${{ inputs.java-early-access == 'true' && format('{0}-ea', inputs.java-version) || inputs.java-version }}
${{ inputs.java-toolchain == 'true' && '17' || '' }}
- name: Set Up Gradle
uses: gradle/actions/setup-gradle@cc4fc85e6b35bafd578d5ffbc76a5518407e1af0 # v4.2.1
uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
with:
cache-read-only: false
develocity-access-key: ${{ inputs.develocity-access-key }}
develocity-token-expiry: 4
- name: Configure Gradle Properties
shell: bash
run: |
@@ -20,7 +20,7 @@ runs:
using: composite
steps:
- name: Set Up JFrog CLI
uses: jfrog/setup-jfrog-cli@dff217c085c17666e8849ebdbf29c8fe5e3995e6 # v4.5.2
uses: jfrog/setup-jfrog-cli@9fe0f98bd45b19e6e931d457f4e98f8f84461fb5 # v4.4.1
env:
JF_ENV_SPRING: ${{ inputs.jfrog-cli-config-token }}
- name: Download Release Artifacts
@@ -21,7 +21,7 @@ jobs:
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
publish: true
- name: Deploy
uses: spring-io/artifactory-deploy-action@dc1913008c0599f0c4b1fdafb6ff3c502b3565ea # v0.0.2
uses: spring-io/artifactory-deploy-action@26bbe925a75f4f863e1e529e85be2d0093cac116 # v0.0.1
with:
artifact-properties: |
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
+14 -3
View File
@@ -9,11 +9,22 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Check Out Code
- name: Set Up JDK 17
uses: actions/setup-java@v4
with:
distribution: 'liberica'
java-version: '17'
- name: Check Out
uses: actions/checkout@v4
- name: Validate Gradle Wrapper
uses: gradle/actions/wrapper-validation@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
- name: Set Up Gradle
uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
- name: Build
id: build
uses: ./.github/actions/build
env:
CI: 'true'
GRADLE_ENTERPRISE_URL: 'https://ge.spring.io'
run: ./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false --no-daemon --no-parallel --continue build
- name: Print JVM Thread Dumps When Cancelled
if: cancelled()
uses: ./.github/actions/print-jvm-thread-dumps
+1 -1
View File
@@ -73,7 +73,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Set up JFrog CLI
uses: jfrog/setup-jfrog-cli@dff217c085c17666e8849ebdbf29c8fe5e3995e6 # v4.5.2
uses: jfrog/setup-jfrog-cli@9fe0f98bd45b19e6e931d457f4e98f8f84461fb5 # v4.4.1
env:
JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }}
- name: Promote build
@@ -12,9 +12,8 @@ permissions:
jobs:
update-antora-ui-spring:
name: Update on Supported Branches
if: ${{ github.repository == 'spring-projects/spring-framework' }}
runs-on: ubuntu-latest
name: Update on Supported Branches
strategy:
matrix:
branch: [ '6.1.x' ]
@@ -26,9 +25,8 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}
antora-file-path: 'framework-docs/antora-playbook.yml'
update-antora-ui-spring-docs-build:
name: Update on docs-build
if: ${{ github.repository == 'spring-projects/spring-framework' }}
runs-on: ubuntu-latest
name: Update on docs-build
steps:
- uses: spring-io/spring-doc-actions/update-antora-spring-ui@5a57bcc6a0da2a1474136cf29571b277850432bc
name: Update
+1 -1
View File
@@ -46,7 +46,7 @@ jobs:
distribution: 'liberica'
java-version: 17
- name: Set Up Gradle
uses: gradle/actions/setup-gradle@cc4fc85e6b35bafd578d5ffbc76a5518407e1af0 # v4.2.1
uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
with:
cache-read-only: false
- name: Configure Gradle Properties
+1 -1
View File
@@ -1,3 +1,3 @@
# Enable auto-env through the sdkman_auto_env config
# Add key=value pairs of SDKs to use below
java=17.0.13-librca
java=17.0.12-librca
+5 -6
View File
@@ -16,7 +16,7 @@ First off, thank you for taking the time to contribute! :+1: :tada:
### Code of Conduct
This project is governed by the [Spring Code of Conduct](https://github.com/spring-projects/spring-framework#coc-ov-file).
This project is governed by the [Spring Code of Conduct](CODE_OF_CONDUCT.adoc).
By participating you are expected to uphold this code.
Please report unacceptable behavior to spring-code-of-conduct@spring.io.
@@ -65,6 +65,10 @@ follow-up reports will need to be created as new issues with a fresh description
#### Submit a Pull Request
1. If you have not previously done so, please sign the
[Contributor License Agreement](https://cla.spring.io/sign/spring). You will be reminded
automatically when you submit the PR.
1. Should you create an issue first? No, just create the pull request and use the
description to provide context and motivation, as you would for an issue. If you want
to start a discussion first or have already created an issue, once a pull request is
@@ -81,11 +85,6 @@ multiple edits or corrections of the same logical change. See
[Rewriting History section of Pro Git](https://git-scm.com/book/en/Git-Tools-Rewriting-History)
for an overview of streamlining the commit history.
1. All commits must include a _Signed-off-by_ trailer at the end of each commit message
to indicate that the contributor agrees to the Developer Certificate of Origin.
For additional details, please refer to the blog post
[Hello DCO, Goodbye CLA: Simplifying Contributions to Spring](https://spring.io/blog/2025/01/06/hello-dco-goodbye-cla-simplifying-contributions-to-spring).
1. Format commit messages using 55 characters for the subject line, 72 characters per line
for the description, followed by the issue fixed, for example, `Closes gh-22276`. See the
[Commit Guidelines section of Pro Git](https://git-scm.com/book/en/Distributed-Git-Contributing-to-a-Project#Commit-Guidelines)
+4 -4
View File
@@ -25,17 +25,17 @@ configure(allprojects) { project ->
repositories {
mavenCentral()
maven {
url = "https://repo.spring.io/milestone"
url "https://repo.spring.io/milestone"
content {
// Netty 5 optional support
includeGroup 'io.projectreactor.netty'
}
}
if (version.contains('-')) {
maven { url = "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/milestone" }
}
if (version.endsWith('-SNAPSHOT')) {
maven { url = "https://repo.spring.io/snapshot" }
maven { url "https://repo.spring.io/snapshot" }
}
}
configurations.all {
@@ -102,7 +102,7 @@ configure([rootProject] + javaProjects) { project ->
// TODO Uncomment link to JUnit 5 docs once we execute Gradle with Java 18+.
// See https://github.com/spring-projects/spring-framework/issues/27497
//
// "https://junit.org/junit5/docs/5.11.4/api/",
// "https://junit.org/junit5/docs/5.11.3/api/",
"https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/",
//"https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/",
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -50,7 +50,7 @@ public class CheckstyleConventions {
project.getPlugins().apply(CheckstylePlugin.class);
project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize().set("1g"));
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
checkstyle.setToolVersion("10.21.1");
checkstyle.setToolVersion("10.20.2");
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
+2 -2
View File
@@ -9,7 +9,7 @@ apply from: "${rootDir}/gradle/publications.gradle"
repositories {
maven {
url = "https://repo.spring.io/release"
url "https://repo.spring.io/release"
}
}
@@ -87,7 +87,7 @@ tasks.register('schemaZip', Zip) {
archiveClassifier.set("schema")
description = "Builds -${archiveClassifier} archive containing all " +
"XSDs for deployment at https://springframework.org/schema."
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
duplicatesStrategy DuplicatesStrategy.EXCLUDE
moduleProjects.each { module ->
def Properties schemas = new Properties();
+1 -1
View File
@@ -37,7 +37,7 @@ javadoc {
repositories {
maven {
url = "https://repo.spring.io/release"
url "https://repo.spring.io/release"
}
}
@@ -578,8 +578,6 @@ Kotlin::
----
======
NOTE: Do not define such beans to be lazy as the `ApplicationContext` will honour that and will not register the method to listen to events.
The method signature once again declares the event type to which it listens,
but, this time, with a flexible name and without implementing a specific listener interface.
The event type can also be narrowed through generics as long as the actual event type
@@ -3,7 +3,7 @@
If a bean is a dependency of another bean, that usually means that one bean is set as a
property of another. Typically you accomplish this with the
xref:core/beans/dependencies/factory-properties-detailed.adoc#beans-ref-element[`<ref/>` element]
xref:core/beans/dependencies/factory-properties-detailed.adoc#beans-ref-element[`<ref/>` element>]
in XML-based metadata or through xref:core/beans/dependencies/factory-autowire.adoc[autowiring].
However, sometimes dependencies between beans are less direct. An example is when a static
@@ -168,8 +168,8 @@ listings shows how to use the `parent` attribute:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<!-- in the child (descendant) context, bean name is the same as the parent bean -->
<bean id="accountService"
<!-- in the child (descendant) context -->
<bean id="accountService" <!-- bean name is the same as the parent bean -->
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
@@ -64,7 +64,7 @@ This prevents the same `@Bean` method from accidentally being invoked through a
Java method call, which helps to reduce subtle bugs that can be hard to track down.
When `@Bean` methods are declared within classes that are not annotated with
`@Configuration`, or when `@Configuration(proxyBeanMethods=false)` is declared,
`@Configuration` - or when `@Configuration(proxyBeanMethods=false)` is declared -,
they are referred to as being processed in a "lite" mode. In such scenarios,
`@Bean` methods are effectively a general-purpose factory method mechanism without
special runtime processing (that is, without generating a CGLIB subclass for it).
@@ -270,7 +270,6 @@ By default, the following `KeyValues` are created:
== HTTP Client Instrumentation
HTTP client exchange observations are created with the name `"http.client.requests"` for blocking and reactive clients.
This observation measures the entire HTTP request/response exchange, from connection establishment up to body deserialization.
Unlike their server counterparts, the instrumentation is implemented directly in the client so the only required step is to configure an `ObservationRegistry` on the client.
[[observability.http-client.resttemplate]]
@@ -286,7 +285,7 @@ Instrumentation uses the `org.springframework.http.client.observation.ClientRequ
|===
|Name | Description
|`method` _(required)_|Name of the HTTP request method or `"none"` if not a well-known method.
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. The protocol, host and port part of the URI are not considered.
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. Only the path part of the URI is considered.
|`client.name` _(required)_|Client name derived from the request URI host.
|`status` _(required)_|HTTP response raw status code, or `"IO_ERROR"` in case of `IOException`, or `"CLIENT_ERROR"` if no response was received.
|`outcome` _(required)_|Outcome of the HTTP client exchange.
@@ -314,7 +313,7 @@ Instrumentation uses the `org.springframework.http.client.observation.ClientRequ
|===
|Name | Description
|`method` _(required)_|Name of the HTTP request method or `"none"` if the request could not be created.
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. The protocol, host and port part of the URI are not considered.
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. Only the path part of the URI is considered.
|`client.name` _(required)_|Client name derived from the request URI host.
|`status` _(required)_|HTTP response raw status code, or `"IO_ERROR"` in case of `IOException`, or `"CLIENT_ERROR"` if no response was received.
|`outcome` _(required)_|Outcome of the HTTP client exchange.
@@ -343,7 +342,7 @@ Instrumentation uses the `org.springframework.web.reactive.function.client.Clien
|===
|Name | Description
|`method` _(required)_|Name of the HTTP request method or `"none"` if not a well-known method.
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. The protocol, host and port part of the URI are not considered.
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. Only the path part of the URI is considered.
|`client.name` _(required)_|Client name derived from the request URI host.
|`status` _(required)_|HTTP response raw status code, or `"IO_ERROR"` in case of `IOException`, or `"CLIENT_ERROR"` if no response was received.
|`outcome` _(required)_|Outcome of the HTTP client exchange.
@@ -938,10 +938,10 @@ method parameters:
| Dynamically set the HTTP method for the request, overriding the annotation's `method` attribute
| `@RequestHeader`
| Add a request header or multiple headers. The argument may be a single value,
a `Collection<?>` of values, `Map<String, ?>`,`MultiValueMap<String, ?>`.
Type conversion is supported for non-String values. Header values are added and
do not override already added header values.
| Add a request header or multiple headers. The argument may be a `Map<String, ?>` or
`MultiValueMap<String, ?>` with multiple headers, a `Collection<?>` of values, or an
individual value. Type conversion is supported for non-String values. This overrides
the annotation's `headers` attribute.
| `@PathVariable`
| Add a variable for expand a placeholder in the request URL. The argument may be a
@@ -14,13 +14,16 @@ For example, `@Autowired lateinit var thing: Thing` implies that a bean
of type `Thing` must be registered in the application context, while `@Autowired lateinit var thing: Thing?`
does not raise an error if such a bean does not exist.
Following the same principle, `@Bean fun play(toy: Toy, car: Car?) = Baz(toy, car)` implies
Following the same principle, `@Bean fun play(toy: Toy, car: Car?) = Baz(toy, Car)` implies
that a bean of type `Toy` must be registered in the application context, while a bean of
type `Car` may or may not exist. The same behavior applies to autowired constructor parameters.
NOTE: If you use bean validation on classes with properties or a primary constructor
with parameters, you may need to use
parameters, you may need to use
{kotlin-docs}/annotations.html#annotation-use-site-targets[annotation use-site targets],
such as `@field:NotNull` or `@get:Size(min=5, max=15)`, as described in
{stackoverflow-site}/a/35853200/1092077[this Stack Overflow response].
@@ -11,21 +11,29 @@ If multiple candidates match, `@Qualifier` can be provided to narrow the candida
override. Alternatively, a candidate whose bean name matches the name of the field will
match.
When using `@MockitoBean`, a new bean will be created if a corresponding bean does not
exist. However, if you would like for the test to fail when a corresponding bean does not
exist, you can set the `enforceOverride` attribute to `true` for example,
`@MockitoBean(enforceOverride = true)`.
To use a by-name override rather than a by-type override, specify the `name` attribute
of the annotation.
[WARNING]
====
Qualifiers, including the name of the field, are used to determine if a separate
`ApplicationContext` needs to be created. If you are using this feature to mock or spy
the same bean in several test classes, make sure to name the field consistently to avoid
the same bean in several tests, make sure to name the field consistently to avoid
creating unnecessary contexts.
====
Each annotation also defines Mockito-specific attributes to fine-tune the mocking behavior.
Each annotation also defines Mockito-specific attributes to fine-tune the mocking details.
The `@MockitoBean` annotation uses the `REPLACE_OR_CREATE`
By default, the `@MockitoBean` annotation uses the `REPLACE_OR_CREATE`
xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-custom[strategy for test bean overriding].
If no existing bean matches, a new bean is created on the fly. However, you can switch to
the `REPLACE` strategy by setting the `enforceOverride` attribute to `true`. See the
following section for an example.
If no existing bean matches, a new bean is created on the fly. As mentioned previously,
you can switch to the `REPLACE` strategy by setting the `enforceOverride` attribute to
`true`.
The `@MockitoSpyBean` annotation uses the `WRAP`
xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-custom[strategy],
@@ -53,17 +61,6 @@ Such fields can therefore be `public`, `protected`, package-private (default vis
or `private` depending on the needs or coding practices of the project.
====
[[spring-testing-annotation-beanoverriding-mockitobean-examples]]
== `@MockitoBean` Examples
When using `@MockitoBean`, a new bean will be created if a corresponding bean does not
exist. However, if you would like for the test to fail when a corresponding bean does not
exist, you can set the `enforceOverride` attribute to `true` for example,
`@MockitoBean(enforceOverride = true)`.
To use a by-name override rather than a by-type override, specify the `name` (or `value`)
attribute of the annotation.
The following example shows how to use the default behavior of the `@MockitoBean` annotation:
[tabs]
@@ -72,13 +69,11 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig.class)
class BeanOverrideTests {
class OverrideBeanTests {
@MockitoBean // <1>
CustomService customService;
// tests...
// test case body...
}
----
<1> Replace the bean with type `CustomService` with a Mockito `mock`.
@@ -87,8 +82,8 @@ Java::
In the example above, we are creating a mock for `CustomService`. If more than one bean
of that type exists, the bean named `customService` is considered. Otherwise, the test
will fail, and you will need to provide a qualifier of some sort to identify which of the
`CustomService` beans you want to override. If no such bean exists, a bean will be
created with an auto-generated bean name.
`CustomService` beans you want to override. If no such bean exists, a bean definition
will be created with an auto-generated bean name.
The following example uses a by-name lookup, rather than a by-type lookup:
@@ -98,43 +93,20 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig.class)
class BeanOverrideTests {
class OverrideBeanTests {
@MockitoBean("service") // <1>
CustomService customService;
// tests...
// test case body...
}
----
<1> Replace the bean named `service` with a Mockito `mock`.
======
If no bean named `service` exists, one is created.
If no bean definition named `service` exists, one is created.
`@MockitoBean` can also be used at the type level:
- on a test class or any superclass or implemented interface in the type hierarchy above
the test class
- on an enclosing class for a `@Nested` test class or on any class or interface in the
type hierarchy or enclosing class hierarchy above the `@Nested` test class
When `@MockitoBean` is declared at the type level, the type of bean (or beans) to mock
must be supplied via the `types` attribute for example,
`@MockitoBean(types = {OrderService.class, UserService.class})`. If multiple candidates
exist in the application context, you can explicitly specify a bean name to mock by
setting the `name` attribute. Note, however, that the `types` attribute must contain a
single type if an explicit bean `name` is configured for example,
`@MockitoBean(name = "ps1", types = PrintingService.class)`.
To support reuse of mock configuration, `@MockitoBean` may be used as a meta-annotation
to create custom _composed annotations_ — for example, to define common mock
configuration in a single annotation that can be reused across a test suite.
`@MockitoBean` can also be used as a repeatable annotation at the type level — for
example, to mock several beans by name.
The following `@SharedMocks` annotation registers two mocks by-type and one mock by-name.
The following example shows how to use the default behavior of the `@MockitoSpyBean` annotation:
[tabs]
======
@@ -142,70 +114,11 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@MockitoBean(types = {OrderService.class, UserService.class}) // <1>
@MockitoBean(name = "ps1", types = PrintingService.class) // <2>
public @interface SharedMocks {
}
----
<1> Register `OrderService` and `UserService` mocks by-type.
<2> Register `PrintingService` mock by-name.
======
The following demonstrates how `@SharedMocks` can be used on a test class.
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig.class)
@SharedMocks // <1>
class BeanOverrideTests {
@Autowired OrderService orderService; // <2>
@Autowired UserService userService; // <2>
@Autowired PrintingService ps1; // <2>
// Inject other components that rely on the mocks.
@Test
void testThatDependsOnMocks() {
// ...
}
}
----
<1> Register common mocks via the custom `@SharedMocks` annotation.
<2> Optionally inject mocks to _stub_ or _verify_ them.
======
TIP: The mocks can also be injected into `@Configuration` classes or other test-related
components in the `ApplicationContext` in order to configure them with Mockito's stubbing
APIs.
[[spring-testing-annotation-beanoverriding-mockitospybean-examples]]
== `@MockitoSpyBean` Examples
The following example shows how to use the default behavior of the `@MockitoSpyBean`
annotation:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig.class)
class BeanOverrideTests {
class OverrideBeanTests {
@MockitoSpyBean // <1>
CustomService customService;
// tests...
// test case body...
}
----
<1> Wrap the bean with type `CustomService` with a Mockito `spy`.
@@ -224,13 +137,12 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig.class)
class BeanOverrideTests {
class OverrideBeanTests {
@MockitoSpyBean("service") // <1>
CustomService customService;
// tests...
// test case body...
}
----
<1> Wrap the bean named `service` with a Mockito `spy`.
@@ -92,10 +92,8 @@ Java::
[TIP]
====
To locate the factory method to invoke, Spring searches in the class in which the
`@TestBean` field is declared, in one of its superclasses, or in any implemented
interfaces. If the `@TestBean` field is declared in a `@Nested` test class, the enclosing
class hierarchy will also be searched.
Spring searches for the factory method to invoke in the test class, in the test class
hierarchy, and in the enclosing class hierarchy for a `@Nested` test class.
Alternatively, a factory method in an external class can be referenced via its
fully-qualified method name following the syntax `<fully-qualified class name>#<method name>`
@@ -193,7 +193,7 @@ Kotlin::
[[webtestclient-fn-config]]
=== Bind to Router Function
This setup allows you to test xref:web/webflux-functional.adoc[functional endpoints] via
This setup allows you to test <<web-reactive.adoc#webflux-fn, functional endpoints>> via
mock request and response objects, without a running server.
For WebFlux, use the following which delegates to `RouterFunctions.toWebHandler` to
@@ -1,6 +1,5 @@
[[webflux-cors]]
= CORS
[.small]#xref:web/webmvc-cors.adoc[See equivalent in the Servlet stack]#
Spring WebFlux lets you handle CORS (Cross-Origin Resource Sharing). This section
@@ -365,7 +364,7 @@ Kotlin::
You can apply CORS support through the built-in
{spring-framework-api}/web/cors/reactive/CorsWebFilter.html[`CorsWebFilter`], which is a
good fit with xref:web/webflux-functional.adoc[functional endpoints].
good fit with <<webflux-fn, functional endpoints>>.
NOTE: If you try to use the `CorsFilter` with Spring Security, keep in mind that Spring
Security has {docs-spring-security}/servlet/integrations/cors.html[built-in support] for
@@ -1,6 +1,5 @@
[[webflux-fn]]
= Functional Endpoints
[.small]#xref:web/webmvc-functional.adoc[See equivalent in the Servlet stack]#
Spring WebFlux includes WebFlux.fn, a lightweight functional programming model in which functions
@@ -1,6 +1,5 @@
[[webflux-websocket]]
= WebSockets
[.small]#xref:web/websocket.adoc[See equivalent in the Servlet stack]#
This part of the reference documentation covers support for reactive-stack WebSocket
@@ -605,4 +605,9 @@ For method parameters and returns values, generally, `@HttpExchange` supports a
subset of the method parameters that `@RequestMapping` does. Notably, it excludes any
server-side specific parameter types. For details, see the list for
xref:integration/rest-clients.adoc#rest-http-interface-method-parameters[@HttpExchange] and
xref:web/webflux/controller/ann-methods/arguments.adoc[@RequestMapping].
xref:web/webflux/controller/ann-methods/arguments.adoc[@RequestMapping].
`@HttpExchange` also supports a `headers()` parameter which accepts `"name=value"`-like
pairs like in `@RequestMapping(headers={})` on the client side. On the server side,
this extends to the full syntax that
xref:#webflux-ann-requestmapping-params-and-headers[`@RequestMapping`] supports.
@@ -101,7 +101,7 @@ On that foundation, Spring WebFlux provides a choice of two programming models:
from the `spring-web` module. Both Spring MVC and WebFlux controllers support reactive
(Reactor and RxJava) return types, and, as a result, it is not easy to tell them apart. One notable
difference is that WebFlux also supports reactive `@RequestBody` arguments.
* xref:web/webflux-functional.adoc[Functional Endpoints]: Lambda-based, lightweight, and functional programming model. You can think of
* <<webflux-fn>>: Lambda-based, lightweight, and functional programming model. You can think of
this as a small library or a set of utilities that an application can use to route and
handle requests. The big difference with annotated controllers is that the application
is in charge of request handling from start to finish versus declaring intent through
@@ -1,6 +1,5 @@
[[mvc-cors]]
= CORS
[.small]#xref:web/webflux-cors.adoc[See equivalent in the Reactive stack]#
Spring MVC lets you handle CORS (Cross-Origin Resource Sharing). This section
@@ -1,7 +1,6 @@
[[webmvc-fn]]
= Functional Endpoints
[.small]#xref:web/webflux-functional.adoc[See equivalent in the Reactive stack]#
[.small]#<<web-reactive.adoc#webflux-fn, See equivalent in the Reactive stack>>#
Spring Web MVC includes WebMvc.fn, a lightweight functional programming model in which functions
are used to route and handle requests and contracts are designed for immutability.
@@ -1,7 +1,6 @@
[[websocket]]
= WebSockets
:page-section-summary-toc: 1
[.small]#xref:web/webflux-websocket.adoc[See equivalent in the Reactive stack]#
This part of the reference documentation covers support for Servlet stack, WebSocket
+9 -9
View File
@@ -8,20 +8,20 @@ javaPlatform {
dependencies {
api(platform("com.fasterxml.jackson:jackson-bom:2.18.2"))
api(platform("io.micrometer:micrometer-bom:1.14.3"))
api(platform("io.netty:netty-bom:4.1.117.Final"))
api(platform("io.micrometer:micrometer-bom:1.14.2"))
api(platform("io.netty:netty-bom:4.1.115.Final"))
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
api(platform("io.projectreactor:reactor-bom:2024.0.2"))
api(platform("io.projectreactor:reactor-bom:2024.0.1"))
api(platform("io.rsocket:rsocket-bom:1.1.4"))
api(platform("org.apache.groovy:groovy-bom:4.0.24"))
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
api(platform("org.assertj:assertj-bom:3.27.2"))
api(platform("org.eclipse.jetty:jetty-bom:12.0.16"))
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.16"))
api(platform("org.assertj:assertj-bom:3.26.3"))
api(platform("org.eclipse.jetty:jetty-bom:12.0.15"))
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.15"))
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.1"))
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.3"))
api(platform("org.junit:junit-bom:5.11.4"))
api(platform("org.mockito:mockito-bom:5.15.2"))
api(platform("org.junit:junit-bom:5.11.3"))
api(platform("org.mockito:mockito-bom:5.14.2"))
constraints {
api("com.fasterxml:aalto-xml:1.3.2")
@@ -31,7 +31,7 @@ dependencies {
api("com.google.code.findbugs:findbugs:3.0.1")
api("com.google.code.findbugs:jsr305:3.0.2")
api("com.google.code.gson:gson:2.11.0")
api("com.google.protobuf:protobuf-java-util:4.29.3")
api("com.google.protobuf:protobuf-java-util:4.28.3")
api("com.h2database:h2:2.3.232")
api("com.jayway.jsonpath:json-path:2.9.0")
api("com.oracle.database.jdbc:ojdbc11:21.9.0.0")
+1 -1
View File
@@ -1,4 +1,4 @@
version=6.2.2
version=6.2.1
org.gradle.caching=true
org.gradle.jvmargs=-Xmx2048m
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+2 -1
View File
@@ -86,7 +86,8 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -99,7 +99,7 @@ abstract class AbstractProxyExceptionHandlingTests {
assertThat(throwableSeenByInterceptor).isSameAs(undeclaredCheckedException);
assertThat(throwableSeenByCaller)
.isInstanceOf(UndeclaredThrowableException.class)
.cause().isSameAs(undeclaredCheckedException);
.hasCauseReference(undeclaredCheckedException);
}
@Test
@@ -147,7 +147,7 @@ abstract class AbstractProxyExceptionHandlingTests {
invokeProxy();
assertThat(throwableSeenByCaller)
.isInstanceOf(UndeclaredThrowableException.class)
.cause().isSameAs(undeclaredCheckedException);
.hasCauseReference(undeclaredCheckedException);
}
@Test
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -95,7 +95,7 @@ public abstract class BeanFactoryAnnotationUtils {
// Full qualifier matching supported.
return qualifiedBeanOfType(lbf, beanType, qualifier);
}
else if (beanFactory.containsBean(qualifier) && beanFactory.isTypeMatch(qualifier, beanType)) {
else if (beanFactory.containsBean(qualifier)) {
// Fallback: target bean at least found by bean name.
return beanFactory.getBean(qualifier, beanType);
}
@@ -110,16 +110,16 @@ public abstract class BeanFactoryAnnotationUtils {
/**
* Obtain a bean of type {@code T} from the given {@code BeanFactory} declaring a qualifier
* (for example, {@code <qualifier>} or {@code @Qualifier}) matching the given qualifier).
* @param beanFactory the factory to get the target bean from
* @param bf the factory to get the target bean from
* @param beanType the type of bean to retrieve
* @param qualifier the qualifier for selecting between multiple bean matches
* @return the matching bean of type {@code T} (never {@code null})
*/
private static <T> T qualifiedBeanOfType(ListableBeanFactory beanFactory, Class<T> beanType, String qualifier) {
String[] candidateBeans = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, beanType);
private static <T> T qualifiedBeanOfType(ListableBeanFactory bf, Class<T> beanType, String qualifier) {
String[] candidateBeans = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(bf, beanType);
String matchingBean = null;
for (String beanName : candidateBeans) {
if (isQualifierMatch(qualifier::equals, beanName, beanFactory)) {
if (isQualifierMatch(qualifier::equals, beanName, bf)) {
if (matchingBean != null) {
throw new NoUniqueBeanDefinitionException(beanType, matchingBean, beanName);
}
@@ -127,11 +127,11 @@ public abstract class BeanFactoryAnnotationUtils {
}
}
if (matchingBean != null) {
return beanFactory.getBean(matchingBean, beanType);
return bf.getBean(matchingBean, beanType);
}
else if (beanFactory.containsBean(qualifier) && beanFactory.isTypeMatch(qualifier, beanType)) {
else if (bf.containsBean(qualifier)) {
// Fallback: target bean at least found by bean name - probably a manually registered singleton.
return beanFactory.getBean(qualifier, beanType);
return bf.getBean(qualifier, beanType);
}
else {
throw new NoSuchBeanDefinitionException(qualifier, "No matching " + beanType.getSimpleName() +
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -991,11 +991,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
*/
@Nullable
private FactoryBean<?> getSingletonFactoryBeanForTypeCheck(String beanName, RootBeanDefinition mbd) {
boolean locked = this.singletonLock.tryLock();
if (!locked) {
return null;
}
this.singletonLock.lock();
try {
BeanWrapper bw = this.factoryBeanInstanceCache.get(beanName);
if (bw != null) {
@@ -177,7 +177,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
/** Map from bean name to merged BeanDefinitionHolder. */
private final Map<String, BeanDefinitionHolder> mergedBeanDefinitionHolders = new ConcurrentHashMap<>(256);
/** Set of bean definition names with a primary marker. */
// Set of bean definition names with a primary marker. */
private final Set<String> primaryBeanNames = ConcurrentHashMap.newKeySet(16);
/** Map of singleton and non-singleton bean names, keyed by dependency type. */
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -279,25 +279,7 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
if (logger.isDebugEnabled()) {
logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
}
try {
beforeSingletonCreation(beanName);
}
catch (BeanCurrentlyInCreationException ex) {
if (locked) {
throw ex;
}
// Try late locking for waiting on specific bean to be finished.
this.singletonLock.lock();
locked = true;
// Singleton object should have appeared in the meantime.
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject != null) {
return singletonObject;
}
beforeSingletonCreation(beanName);
}
beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = (locked && this.suppressedExceptions == null);
if (recordSuppressedExceptions) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -23,6 +23,7 @@ import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.testfixture.beans.TestBean;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* @author Juergen Hoeller
@@ -55,6 +56,9 @@ class BeanFactoryLockingTests {
@Override
public void afterPropertiesSet() throws Exception {
Thread thread = new Thread(() -> {
// Fail for circular reference from other thread
assertThatExceptionOfType(BeanCurrentlyInCreationException.class).isThrownBy(() ->
beanFactory.getBean(ThreadDuringInitialization.class));
// Leniently create unrelated other bean outside of singleton lock
assertThat(beanFactory.getBean(TestBean.class).getName()).isEqualTo("tb");
// Creation attempt in other thread was successful
@@ -1060,9 +1060,7 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
}
int size = (indexes.last() < this.autoGrowCollectionLimit ? indexes.last() + 1 : 0);
List<V> list = (List<V>) CollectionFactory.createCollection(paramType, size);
for (int i = 0; i < size; i++) {
list.add(null);
}
indexes.forEach(i -> list.add(null));
for (int index : indexes) {
list.set(index, (V) createObject(elementType, paramPath + "[" + index + "].", valueResolver));
}
@@ -1082,7 +1080,7 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
}
int startIdx = paramPath.length() + 1;
int endIdx = name.indexOf(']', startIdx);
String nestedPath = ((name.length() > endIdx + 1) ? name.substring(0, endIdx + 2) : "");
String nestedPath = name.substring(0, endIdx + 2);
boolean quoted = (endIdx - startIdx > 2 && name.charAt(startIdx) == '\'' && name.charAt(endIdx - 1) == '\'');
String key = (quoted ? name.substring(startIdx + 1, endIdx - 1) : name.substring(startIdx, endIdx));
if (map == null) {
@@ -1116,7 +1114,7 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
SortedSet<Integer> indexes = null;
for (String name : valueResolver.getNames()) {
if (name.startsWith(paramPath + "[")) {
int endIndex = name.indexOf(']', paramPath.length() + 1);
int endIndex = name.indexOf(']', paramPath.length() + 2);
String rawIndex = name.substring(paramPath.length() + 1, endIndex);
int index = Integer.parseInt(rawIndex);
indexes = (indexes != null ? indexes : new TreeSet<>());
@@ -367,7 +367,7 @@ public class MethodValidationAdapter implements MethodValidator {
container = null;
}
if (node.getKind().equals(ElementKind.PROPERTY) || node.getKind().equals(ElementKind.BEAN)) {
if (node.getKind().equals(ElementKind.PROPERTY)) {
nestedViolations
.computeIfAbsent(parameterNode, k ->
new ParamErrorsBuilder(parameter, value, container, index, key))
@@ -213,8 +213,8 @@ class ApplicationListenerMethodAdapterTests extends AbstractApplicationEventList
@Test
void invokeListenerWithWrongGenericPayload() {
Method method = ReflectionUtils.findMethod(
SampleEvents.class, "handleGenericStringPayload", EntityWrapper.class);
Method method = ReflectionUtils.findMethod
(SampleEvents.class, "handleGenericStringPayload", EntityWrapper.class);
EntityWrapper<Integer> payload = new EntityWrapper<>(123);
invokeListener(method, new PayloadApplicationEvent<>(this, payload));
verify(this.sampleEvents, times(0)).handleGenericStringPayload(any());
@@ -67,7 +67,7 @@ class ConversionServiceFactoryBeanTests {
converters.add(new ConverterFactory<String, Bar>() {
@Override
public <T extends Bar> Converter<String, T> getConverter(Class<T> targetType) {
return new Converter<>() {
return new Converter<> () {
@SuppressWarnings("unchecked")
@Override
public T convert(String source) {
@@ -121,24 +121,6 @@ class DataBinderConstructTests {
assertThat(list.get(2).param1()).isEqualTo("value3");
}
@Test // gh-34145
void listBindingWithNonconsecutiveIndices() {
MapValueResolver valueResolver = new MapValueResolver(Map.of(
"dataClassList[0].param1", "value1", "dataClassList[0].param2", "true",
"dataClassList[1].param1", "value2", "dataClassList[1].param2", "true",
"dataClassList[3].param1", "value3", "dataClassList[3].param2", "true"));
DataBinder binder = initDataBinder(ListDataClass.class);
binder.construct(valueResolver);
ListDataClass dataClass = getTarget(binder);
List<DataClass> list = dataClass.dataClassList();
assertThat(list.get(0).param1()).isEqualTo("value1");
assertThat(list.get(1).param1()).isEqualTo("value2");
assertThat(list.get(3).param1()).isEqualTo("value3");
}
@Test
void mapBinding() {
MapValueResolver valueResolver = new MapValueResolver(Map.of(
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -180,7 +180,7 @@ public final class GenericTypeResolver {
generics[i] = resolvedTypeArgument;
}
else {
generics[i] = ResolvableType.forType(typeArgument).resolveType();
generics[i] = ResolvableType.forType(typeArgument);
}
}
else if (typeArgument instanceof ParameterizedType) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -410,9 +410,8 @@ public class ResolvableType implements Serializable {
}
matchedBefore.put(this.type, other.type);
for (int i = 0; i < ourGenerics.length; i++) {
ResolvableType otherGeneric = otherGenerics[i];
if (!ourGenerics[i].isAssignableFrom(otherGeneric,
!otherGeneric.isUnresolvableTypeVariable(), matchedBefore, upUntilUnresolvable)) {
if (!ourGenerics[i].isAssignableFrom(otherGenerics[i],
!other.hasUnresolvableGenerics(), matchedBefore, upUntilUnresolvable)) {
return false;
}
}
@@ -1730,16 +1729,8 @@ public class ResolvableType implements Serializable {
* @return {@code true} if these bounds are assignable from all types
*/
public boolean isAssignableFrom(ResolvableType[] types, @Nullable Map<Type, Type> matchedBefore) {
for (ResolvableType bound : this.bounds) {
boolean matched = false;
for (ResolvableType type : types) {
if (this.kind == Kind.UPPER ? bound.isAssignableFrom(type, false, matchedBefore, false) :
type.isAssignableFrom(bound, false, matchedBefore, false)) {
matched = true;
break;
}
}
if (!matched) {
for (ResolvableType type : types) {
if (!isAssignableFrom(type, matchedBefore)) {
return false;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -40,7 +40,6 @@ import java.nio.file.Path;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.NavigableSet;
@@ -646,15 +645,13 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
}
}
if (currentPrefix != null) {
if (checkPathWithinPackage(path.substring(currentPrefix.length()))) {
// A prefix match found, potentially to be turned into a common parent cache entry.
if (commonPrefix == null || !commonUnique || currentPrefix.length() > commonPrefix.length()) {
commonPrefix = currentPrefix;
existingPath = path;
}
else if (currentPrefix.equals(commonPrefix)) {
commonUnique = false;
}
// A prefix match found, potentially to be turned into a common parent cache entry.
if (commonPrefix == null || !commonUnique || currentPrefix.length() > commonPrefix.length()) {
commonPrefix = currentPrefix;
existingPath = path;
}
else if (currentPrefix.equals(commonPrefix)) {
commonUnique = false;
}
}
else if (actualRootPath == null || path.length() > actualRootPath.length()) {
@@ -807,7 +804,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
if (separatorIndex == -1) {
separatorIndex = urlFile.indexOf(ResourceUtils.JAR_URL_SEPARATOR);
}
if (separatorIndex >= 0) {
if (separatorIndex != -1) {
jarFileUrl = urlFile.substring(0, separatorIndex);
rootEntryPath = urlFile.substring(separatorIndex + 2); // both separators are 2 chars
NavigableSet<String> entriesCache = this.jarEntriesCache.get(jarFileUrl);
@@ -834,17 +831,11 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
if (con instanceof JarURLConnection jarCon) {
// Should usually be the case for traditional JAR files.
try {
jarFile = jarCon.getJarFile();
jarFileUrl = jarCon.getJarFileURL().toExternalForm();
JarEntry jarEntry = jarCon.getJarEntry();
rootEntryPath = (jarEntry != null ? jarEntry.getName() : "");
closeJarFile = !jarCon.getUseCaches();
}
catch (FileNotFoundException ex) {
// Happens in case of cached root directory without specific subdirectory present.
return Collections.emptySet();
}
jarFile = jarCon.getJarFile();
jarFileUrl = jarCon.getJarFileURL().toExternalForm();
JarEntry jarEntry = jarCon.getJarEntry();
rootEntryPath = (jarEntry != null ? jarEntry.getName() : "");
closeJarFile = !jarCon.getUseCaches();
}
else {
// No JarURLConnection -> need to resort to URL file parsing.
@@ -881,13 +872,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
}
Set<Resource> result = new LinkedHashSet<>(64);
NavigableSet<String> entriesCache = new TreeSet<>();
Iterator<String> entryIterator = jarFile.stream().map(JarEntry::getName).sorted().iterator();
while (entryIterator.hasNext()) {
String entryPath = entryIterator.next();
int entrySeparatorIndex = entryPath.indexOf(ResourceUtils.JAR_URL_SEPARATOR);
if (entrySeparatorIndex >= 0) {
entryPath = entryPath.substring(entrySeparatorIndex + ResourceUtils.JAR_URL_SEPARATOR.length());
}
for (String entryPath : jarFile.stream().map(JarEntry::getName).sorted().toList()) {
entriesCache.add(entryPath);
if (entryPath.startsWith(rootEntryPath)) {
String relativePath = entryPath.substring(rootEntryPath.length());
@@ -1118,10 +1103,6 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
return (path.startsWith("/") ? path.substring(1) : path);
}
private static boolean checkPathWithinPackage(String path) {
return (path.contains("/") && !path.contains(ResourceUtils.JAR_URL_SEPARATOR));
}
/**
* Inner delegate class, avoiding a hard JBoss VFS API dependency at runtime.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-2022 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.
@@ -51,11 +51,10 @@ public class FlightRecorderApplicationStartup implements ApplicationStartup {
@Override
public StartupStep start(String name) {
Long parentId = this.currentSteps.getFirst();
long sequenceId = this.currentSequenceId.incrementAndGet();
this.currentSteps.offerFirst(sequenceId);
return new FlightRecorderStartupStep(sequenceId, name,
parentId, committedStep -> this.currentSteps.removeFirstOccurrence(sequenceId));
this.currentSteps.getFirst(), committedStep -> this.currentSteps.removeFirstOccurrence(sequenceId));
}
}
@@ -519,7 +519,7 @@ final class PlaceholderParser {
@Override
public String resolve(PartResolutionContext resolutionContext) {
String value = resolveRecursively(resolutionContext);
String value = resolveRecursively(resolutionContext, this.key);
if (value != null) {
return value;
}
@@ -528,17 +528,6 @@ final class PlaceholderParser {
}
return resolutionContext.handleUnresolvablePlaceholder(this.key, text());
}
@Nullable
private String resolveRecursively(PartResolutionContext resolutionContext) {
if (!this.text().equals(this.key)) {
String value = resolveRecursively(resolutionContext, this.text());
if (value != null) {
return value;
}
}
return resolveRecursively(resolutionContext, this.key);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-2023 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.
@@ -719,7 +719,8 @@ public abstract class ReflectionUtils {
// Keep backing up the inheritance hierarchy.
Class<?> targetClass = clazz;
do {
for (Field field : getDeclaredFields(targetClass)) {
Field[] fields = getDeclaredFields(targetClass);
for (Field field : fields) {
if (ff != null && !ff.matches(field)) {
continue;
}
@@ -148,8 +148,8 @@ class GeneratedClassesTests {
"one", TestComponent.class, emptyTypeCustomizer);
GeneratedClass generatedClass2 = this.generatedClasses.getOrAddForFeatureComponent(
"one", TestComponent.class, emptyTypeCustomizer);
GeneratedClass generatedClass3 = prefixed.getOrAddForFeatureComponent(
"one", TestComponent.class, emptyTypeCustomizer);
GeneratedClass generatedClass3 = prefixed.getOrAddForFeatureComponent
("one", TestComponent.class, emptyTypeCustomizer);
GeneratedClass generatedClass4 = prefixed.getOrAddForFeatureComponent(
"one", TestComponent.class, emptyTypeCustomizer);
assertThat(generatedClass1).isSameAs(generatedClass2).isNotSameAs(generatedClass3);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -211,14 +211,6 @@ class GenericTypeResolverTests {
assertThat(ResolvableType.forType(resolved).getGeneric(0).getGeneric(0).resolve()).isEqualTo(String.class);
}
@Test
void resolvedTypeWithBase() {
Type type = method(WithBaseTypes.class, "get").getGenericReturnType();
Type resolvedType = resolveType(type, WithBaseTypes.class);
ParameterizedTypeReference<List<A>> reference = new ParameterizedTypeReference<>() {};
assertThat(resolvedType).isEqualTo(reference.getType());
}
private static Method method(Class<?> target, String methodName, Class<?>... parameterTypes) {
Method method = findMethod(target, methodName, parameterTypes);
assertThat(method).describedAs(target.getName() + "#" + methodName).isNotNull();
@@ -406,12 +398,4 @@ class GenericTypeResolverTests {
public interface StringListOfListSupplier extends ListOfListSupplier<String> {
}
static class WithBaseTypes {
<T extends A> List<T> get() {
return List.of();
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -1189,11 +1189,10 @@ class ResolvableTypeTests {
}
@Test
void isAssignableFromForUnresolvedWildcard() {
void isAssignableFromForUnresolvedWildcards() {
ResolvableType wildcard = ResolvableType.forInstance(new Wildcard<>());
ResolvableType wildcardFixed = ResolvableType.forInstance(new WildcardFixed());
ResolvableType wildcardConcrete = ResolvableType.forClassWithGenerics(Wildcard.class, CharSequence.class);
ResolvableType wildcardConsumer = ResolvableType.forInstance(new WildcardConsumer<>());
ResolvableType wildcardConcrete = ResolvableType.forClassWithGenerics(Wildcard.class, Number.class);
assertThat(wildcard.isAssignableFrom(wildcardFixed)).isTrue();
assertThat(wildcard.isAssignableFromResolvedPart(wildcardFixed)).isTrue();
@@ -1207,38 +1206,6 @@ class ResolvableTypeTests {
assertThat(wildcardConcrete.isAssignableFromResolvedPart(wildcard)).isTrue();
assertThat(wildcardConcrete.isAssignableFrom(wildcardFixed)).isFalse();
assertThat(wildcardConcrete.isAssignableFromResolvedPart(wildcardFixed)).isFalse();
assertThat(wildcardConsumer.as(Consumer.class).getGeneric().isAssignableFrom(wildcard)).isFalse();
assertThat(wildcardConsumer.as(Consumer.class).getGeneric().isAssignableFromResolvedPart(wildcard)).isTrue();
}
@Test
void isAssignableFromForUnresolvedDoubleWildcard() {
ResolvableType wildcard = ResolvableType.forInstance(new DoubleWildcard<>());
ResolvableType wildcardFixed = ResolvableType.forInstance(new DoubleWildcardFixed());
ResolvableType wildcardConsumer = ResolvableType.forInstance(new DoubleWildcardConsumer<>());
assertThat(wildcard.isAssignableFrom(wildcardFixed)).isTrue();
assertThat(wildcard.isAssignableFromResolvedPart(wildcardFixed)).isTrue();
assertThat(wildcardFixed.isAssignableFrom(wildcard)).isFalse();
assertThat(wildcardFixed.isAssignableFromResolvedPart(wildcard)).isFalse();
assertThat(wildcardConsumer.as(Consumer.class).getGeneric().isAssignableFrom(wildcard)).isTrue();
assertThat(wildcardConsumer.as(Consumer.class).getGeneric().isAssignableFromResolvedPart(wildcard)).isTrue();
}
@Test
void strictGenericsMatching() {
ResolvableType consumerUnresolved = ResolvableType.forClass(Consumer.class);
ResolvableType consumerObject = ResolvableType.forClassWithGenerics(Consumer.class, Object.class);
ResolvableType consumerNestedUnresolved = ResolvableType.forClassWithGenerics(Consumer.class, ResolvableType.forClass(Consumer.class));
assertThat(consumerUnresolved.isAssignableFrom(consumerObject)).isTrue();
assertThat(consumerUnresolved.isAssignableFromResolvedPart(consumerObject)).isTrue();
assertThat(consumerObject.isAssignableFrom(consumerUnresolved)).isTrue();
assertThat(consumerObject.isAssignableFromResolvedPart(consumerUnresolved)).isTrue();
assertThat(consumerUnresolved.isAssignableFrom(consumerNestedUnresolved)).isTrue();
assertThat(consumerUnresolved.isAssignableFromResolvedPart(consumerNestedUnresolved)).isTrue();
assertThat(consumerObject.isAssignableFrom(consumerNestedUnresolved)).isFalse();
assertThat(consumerObject.isAssignableFromResolvedPart(consumerNestedUnresolved)).isFalse();
}
@Test
@@ -1785,28 +1752,12 @@ class ResolvableTypeTests {
}
public interface Consumer<T> {
public class Wildcard<T extends Number> {
}
public class Wildcard<T extends CharSequence> {
public class WildcardFixed extends Wildcard<Integer> {
}
public class WildcardFixed extends Wildcard<String> {
}
public class WildcardConsumer<T extends CharSequence & Serializable> implements Consumer<Wildcard<T>> {
}
public class DoubleWildcard<T extends CharSequence & Serializable> {
}
public class DoubleWildcardFixed extends DoubleWildcard<String> {
}
public class DoubleWildcardConsumer<T extends CharSequence & Serializable> implements Consumer<DoubleWildcard<T>> {
}
interface VariableNameSwitch<V, K> extends MultiValueMap<K, V> {
}
@@ -24,7 +24,6 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.InOrder;
import org.springframework.util.PlaceholderParser.ParsedValue;
import org.springframework.util.PlaceholderParser.TextPart;
@@ -34,7 +33,6 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -222,37 +220,12 @@ class PlaceholderParserTests {
);
}
@ParameterizedTest(name = "{0} -> {1}")
@MethodSource("exactMatchPlaceholders")
void placeholdersWithExactMatchAreConsidered(String text, String expected) {
Properties properties = new Properties();
properties.setProperty("prefix://my-service", "example-service");
properties.setProperty("px", "prefix");
properties.setProperty("p1", "${prefix://my-service}");
assertThat(this.parser.replacePlaceholders(text, properties::getProperty)).isEqualTo(expected);
}
static Stream<Arguments> exactMatchPlaceholders() {
return Stream.of(
Arguments.of("${prefix://my-service}", "example-service"),
Arguments.of("${p1}", "example-service")
);
}
@Test
void parseWithKeyEqualsToText() {
PlaceholderResolver resolver = mockPlaceholderResolver("firstName", "Steve");
assertThat(this.parser.replacePlaceholders("${firstName}", resolver))
.isEqualTo("Steve");
verifyPlaceholderResolutions(resolver, "firstName");
}
@Test
void parseWithHardcodedFallback() {
PlaceholderResolver resolver = mockPlaceholderResolver();
assertThat(this.parser.replacePlaceholders("${firstName:Steve}", resolver))
.isEqualTo("Steve");
verifyPlaceholderResolutions(resolver, "firstName:Steve", "firstName");
verifyPlaceholderResolutions(resolver, "firstName");
}
@Test
@@ -260,7 +233,7 @@ class PlaceholderParserTests {
PlaceholderResolver resolver = mockPlaceholderResolver("firstName", "John");
assertThat(this.parser.replacePlaceholders("${first${invalid:Name}}", resolver))
.isEqualTo("John");
verifyPlaceholderResolutions(resolver, "invalid:Name", "invalid", "firstName");
verifyPlaceholderResolutions(resolver, "invalid", "firstName");
}
@Test
@@ -375,9 +348,8 @@ class PlaceholderParserTests {
}
void verifyPlaceholderResolutions(PlaceholderResolver mock, String... placeholders) {
InOrder ordered = inOrder(mock);
for (String placeholder : placeholders) {
ordered.verify(mock).resolvePlaceholder(placeholder);
verify(mock).resolvePlaceholder(placeholder);
}
verifyNoMoreInteractions(mock);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -44,7 +44,6 @@ class PropertyPlaceholderHelperTests {
private final PropertyPlaceholderHelper helper = new PropertyPlaceholderHelper("${", "}");
@Test
void withProperties() {
String text = "foo=${foo}";
@@ -117,8 +116,8 @@ class PropertyPlaceholderHelperTests {
props.setProperty("foo", "bar");
PropertyPlaceholderHelper helper = new PropertyPlaceholderHelper("${", "}", null, null, false);
assertThatExceptionOfType(PlaceholderResolutionException.class)
.isThrownBy(() -> helper.replacePlaceholders(text, props));
assertThatExceptionOfType(PlaceholderResolutionException.class).isThrownBy(() ->
helper.replacePlaceholders(text, props));
}
@Nested
@@ -154,30 +153,13 @@ class PropertyPlaceholderHelperTests {
);
}
@ParameterizedTest(name = "{0} -> {1}")
@MethodSource("exactMatchPlaceholders")
void placeholdersWithExactMatchAreConsidered(String text, String expected) {
Properties properties = new Properties();
properties.setProperty("prefix://my-service", "example-service");
properties.setProperty("px", "prefix");
properties.setProperty("p1", "${prefix://my-service}");
assertThat(this.helper.replacePlaceholders(text, properties)).isEqualTo(expected);
}
static Stream<Arguments> exactMatchPlaceholders() {
return Stream.of(
Arguments.of("${prefix://my-service}", "example-service"),
Arguments.of("${p1}", "example-service")
);
}
}
private static PlaceholderResolver mockPlaceholderResolver(String... pairs) {
PlaceholderResolver mockPlaceholderResolver(String... pairs) {
if (pairs.length % 2 == 1) {
throw new IllegalArgumentException("size must be even, it is a set of key=value pairs");
}
PlaceholderResolver resolver = mock();
PlaceholderResolver resolver = mock(PlaceholderResolver.class);
for (int i = 0; i < pairs.length; i += 2) {
String key = pairs[i];
String value = pairs[i + 1];
@@ -186,4 +168,5 @@ class PropertyPlaceholderHelperTests {
return resolver;
}
}
@@ -229,9 +229,8 @@ public class FunctionReference extends SpelNodeImpl {
ReflectionHelper.convertAllMethodHandleArguments(converter, functionArgs, methodHandle, varArgPosition);
if (isSuspectedVarargs) {
if (declaredParamCount == 1 && !methodHandle.isVarargsCollector()) {
// We only repackage the arguments if the MethodHandle accepts a single
// argument AND the MethodHandle is not a "varargs collector" -- for example,
if (declaredParamCount == 1) {
// We only repackage the varargs if it is the ONLY argument -- for example,
// when we are dealing with a bound MethodHandle.
functionArgs = ReflectionHelper.setupArgumentsForVarargsInvocation(
methodHandle.type().parameterArray(), functionArgs);
@@ -108,16 +108,11 @@ class TestScenarioCreator {
"formatObjectVarargs", MethodType.methodType(String.class, String.class, Object[].class));
testContext.registerFunction("formatObjectVarargs", formatObjectVarargs);
// #formatPrimitiveVarargs(format, args...)
// #formatObjectVarargs(format, args...)
MethodHandle formatPrimitiveVarargs = MethodHandles.lookup().findStatic(TestScenarioCreator.class,
"formatPrimitiveVarargs", MethodType.methodType(String.class, String.class, int[].class));
testContext.registerFunction("formatPrimitiveVarargs", formatPrimitiveVarargs);
// #varargsFunctionHandle(args...)
MethodHandle varargsFunctionHandle = MethodHandles.lookup().findStatic(TestScenarioCreator.class,
"varargsFunction", MethodType.methodType(String.class, String[].class));
testContext.registerFunction("varargsFunctionHandle", varargsFunctionHandle);
// #add(int, int)
MethodHandle add = MethodHandles.lookup().findStatic(TestScenarioCreator.class,
"add", MethodType.methodType(int.class, int.class, int.class));
@@ -79,8 +79,6 @@ class VariableAndFunctionTests extends AbstractExpressionTests {
@Test
void functionWithVarargs() {
// static String varargsFunction(String... strings) -> Arrays.toString(strings)
evaluate("#varargsFunction()", "[]", String.class);
evaluate("#varargsFunction(new String[0])", "[]", String.class);
evaluate("#varargsFunction('a')", "[a]", String.class);
@@ -243,27 +241,6 @@ class VariableAndFunctionTests extends AbstractExpressionTests {
evaluate("#formatObjectVarargs('x -> %s %s %s', {'a', 'b', 'c'})", expected, String.class);
}
@Test // gh-34109
void functionViaMethodHandleForStaticMethodThatAcceptsOnlyVarargs() {
// #varargsFunctionHandle: static String varargsFunction(String... strings) -> Arrays.toString(strings)
evaluate("#varargsFunctionHandle()", "[]", String.class);
evaluate("#varargsFunctionHandle(new String[0])", "[]", String.class);
evaluate("#varargsFunctionHandle('a')", "[a]", String.class);
evaluate("#varargsFunctionHandle('a','b','c')", "[a, b, c]", String.class);
evaluate("#varargsFunctionHandle(new String[]{'a','b','c'})", "[a, b, c]", String.class);
// Conversion from int to String
evaluate("#varargsFunctionHandle(25)", "[25]", String.class);
evaluate("#varargsFunctionHandle('b',25)", "[b, 25]", String.class);
evaluate("#varargsFunctionHandle(new int[]{1, 2, 3})", "[1, 2, 3]", String.class);
// Strings that contain a comma
evaluate("#varargsFunctionHandle('a,b')", "[a,b]", String.class);
evaluate("#varargsFunctionHandle('a', 'x,y', 'd')", "[a, x,y, d]", String.class);
// null values
evaluate("#varargsFunctionHandle(null)", "[null]", String.class);
evaluate("#varargsFunctionHandle('a',null,'b')", "[a, null, b]", String.class);
}
@Test
void functionMethodMustBeStatic() throws Exception {
SpelExpressionParser parser = new SpelExpressionParser();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-2023 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.
@@ -21,8 +21,6 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.sql.DataSource;
@@ -68,9 +66,6 @@ public abstract class AbstractJdbcCall {
/** List of RefCursor/ResultSet RowMapper objects. */
private final Map<String, RowMapper<?>> declaredRowMappers = new LinkedHashMap<>();
/** Lock for the compilation step. */
private final Lock compilationLock = new ReentrantLock();
/**
* Has this operation been compiled? Compilation means at least checking
* that a DataSource or JdbcTemplate has been provided.
@@ -289,29 +284,23 @@ public abstract class AbstractJdbcCall {
* @throws org.springframework.dao.InvalidDataAccessApiUsageException if the object hasn't
* been correctly initialized, for example if no DataSource has been provided
*/
public final void compile() throws InvalidDataAccessApiUsageException {
this.compilationLock.lock();
try {
if (!isCompiled()) {
if (getProcedureName() == null) {
throw new InvalidDataAccessApiUsageException("Procedure or Function name is required");
}
try {
this.jdbcTemplate.afterPropertiesSet();
}
catch (IllegalArgumentException ex) {
throw new InvalidDataAccessApiUsageException(ex.getMessage());
}
compileInternal();
this.compiled = true;
if (logger.isDebugEnabled()) {
logger.debug("SqlCall for " + (isFunction() ? "function" : "procedure") +
" [" + getProcedureName() + "] compiled");
}
public final synchronized void compile() throws InvalidDataAccessApiUsageException {
if (!isCompiled()) {
if (getProcedureName() == null) {
throw new InvalidDataAccessApiUsageException("Procedure or Function name is required");
}
try {
this.jdbcTemplate.afterPropertiesSet();
}
catch (IllegalArgumentException ex) {
throw new InvalidDataAccessApiUsageException(ex.getMessage());
}
compileInternal();
this.compiled = true;
if (logger.isDebugEnabled()) {
logger.debug("SqlCall for " + (isFunction() ? "function" : "procedure") +
" [" + getProcedureName() + "] compiled");
}
}
finally {
this.compilationLock.unlock();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -183,13 +183,6 @@ public abstract class JdbcTransactionObjectSupport implements SavepointManager,
catch (SQLFeatureNotSupportedException ex) {
// typically on Oracle - ignore
}
catch (SQLException ex) {
// ignore Microsoft SQLServerException: This operation is not supported.
String msg = ex.getMessage();
if (msg == null || !msg.contains("not supported")) {
throw new TransactionSystemException("Could not explicitly release JDBC savepoint", ex);
}
}
catch (Throwable ex) {
throw new TransactionSystemException("Could not explicitly release JDBC savepoint", ex);
}
@@ -98,7 +98,7 @@ public class CompositeMessageCondition implements MessageCondition<CompositeMess
checkCompatible(other);
List<MessageCondition<?>> otherConditions = other.getMessageConditions();
for (int i = 0; i < this.messageConditions.size(); i++) {
int result = compare(this.messageConditions.get(i), otherConditions.get(i), message);
int result = compare (this.messageConditions.get(i), otherConditions.get(i), message);
if (result != 0) {
return result;
}
@@ -186,7 +186,7 @@ public abstract class SharedEntityManagerCreator {
@SuppressWarnings("serial")
private static class SharedEntityManagerInvocationHandler implements InvocationHandler, Serializable {
private static final Log logger = LogFactory.getLog(SharedEntityManagerInvocationHandler.class);
private final Log logger = LogFactory.getLog(getClass());
private final EntityManagerFactory targetFactory;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -82,9 +82,6 @@ import org.springframework.lang.Nullable;
* <li>{@link ActiveProfiles @ActiveProfiles}</li>
* <li>{@link TestPropertySource @TestPropertySource}</li>
* <li>{@link DynamicPropertySource @DynamicPropertySource}</li>
* <li>{@link org.springframework.test.context.bean.override.convention.TestBean @TestBean}</li>
* <li>{@link org.springframework.test.context.bean.override.mockito.MockitoBean @MockitoBean}</li>
* <li>{@link org.springframework.test.context.bean.override.mockito.MockitoSpyBean @MockitoSpyBean}</li>
* <li>{@link org.springframework.test.annotation.DirtiesContext @DirtiesContext}</li>
* <li>{@link org.springframework.transaction.annotation.Transactional @Transactional}</li>
* <li>{@link org.springframework.test.annotation.Rollback @Rollback}</li>
@@ -25,19 +25,15 @@ import java.lang.annotation.Target;
import org.springframework.aot.hint.annotation.Reflective;
/**
* Mark a <em>composed annotation</em> as eligible for Bean Override processing.
* Mark a composed annotation as eligible for Bean Override processing.
*
* <p>Specifying this annotation registers the configured {@link BeanOverrideProcessor}
* which must be capable of handling the composed annotation and its attributes.
*
* <p>Since the composed annotation will typically only be applied to non-static
* fields, it is expected that the composed annotation is meta-annotated with
* {@link Target @Target(ElementType.FIELD)}. However, certain bean override
* annotations may be declared with an additional {@code ElementType.TYPE} target
* for use at the type level, as is the case for {@code @MockitoBean} which can
* be declared on a field, test class, or test interface.
* <p>Since the composed annotation should only be applied to non-static fields, it is
* expected that it is meta-annotated with {@link Target @Target(ElementType.FIELD)}.
*
* <p>For concrete examples of such composed annotations, see
* <p>For concrete examples, see
* {@link org.springframework.test.context.bean.override.convention.TestBean @TestBean},
* {@link org.springframework.test.context.bean.override.mockito.MockitoBean @MockitoBean}, and
* {@link org.springframework.test.context.bean.override.mockito.MockitoSpyBean @MockitoSpyBean}.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -104,10 +104,11 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
Set<String> generatedBeanNames) {
String beanName = handler.getBeanName();
Assert.state(!BeanFactoryUtils.isFactoryDereference(beanName), () -> """
Unable to override bean '%s'%s: a FactoryBean cannot be overridden. \
To override the bean created by the FactoryBean, remove the '&' prefix."""
.formatted(beanName, forField(handler.getField())));
Field field = handler.getField();
Assert.state(!BeanFactoryUtils.isFactoryDereference(beanName),() -> """
Unable to override bean '%s' for field '%s.%s': a FactoryBean cannot be overridden. \
To override the bean created by the FactoryBean, remove the '&' prefix.""".formatted(
beanName, field.getDeclaringClass().getSimpleName(), field.getName()));
switch (handler.getStrategy()) {
case REPLACE -> replaceOrCreateBean(beanFactory, handler, generatedBeanNames, true);
@@ -133,6 +134,7 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
// 4) Create bean by-name, with a provided name
String beanName = handler.getBeanName();
Field field = handler.getField();
BeanDefinition existingBeanDefinition = null;
if (beanName == null) {
beanName = getBeanNameForType(beanFactory, handler, requireExistingBean);
@@ -167,10 +169,11 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
existingBeanDefinition = beanFactory.getBeanDefinition(beanName);
}
else if (requireExistingBean) {
Field field = handler.getField();
throw new IllegalStateException(
"Unable to replace bean: there is no bean with name '%s' and type %s%s."
.formatted(beanName, handler.getBeanType(), requiredByField(field)));
throw new IllegalStateException("""
Unable to replace bean: there is no bean with name '%s' and type %s \
(as required by field '%s.%s')."""
.formatted(beanName, handler.getBeanType(),
field.getDeclaringClass().getSimpleName(), field.getName()));
}
// 4) We are creating a bean by-name with the provided beanName.
}
@@ -261,11 +264,13 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
else {
String message = "Unable to select a bean to wrap: ";
if (candidateCount == 0) {
message += "there are no beans of type %s%s.".formatted(beanType, requiredByField(field));
message += "there are no beans of type %s (as required by field '%s.%s')."
.formatted(beanType, field.getDeclaringClass().getSimpleName(), field.getName());
}
else {
message += "found %d beans of type %s%s: %s"
.formatted(candidateCount, beanType, requiredByField(field), candidateNames);
message += "found %d beans of type %s (as required by field '%s.%s'): %s"
.formatted(candidateCount, beanType, field.getDeclaringClass().getSimpleName(),
field.getName(), candidateNames);
}
throw new IllegalStateException(message);
}
@@ -276,9 +281,11 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
// We are wrapping an existing bean by-name.
Set<String> candidates = getExistingBeanNamesByType(beanFactory, handler, false);
if (!candidates.contains(beanName)) {
throw new IllegalStateException(
"Unable to wrap bean: there is no bean with name '%s' and type %s%s."
.formatted(beanName, beanType, requiredByField(field)));
throw new IllegalStateException("""
Unable to wrap bean: there is no bean with name '%s' and type %s \
(as required by field '%s.%s')."""
.formatted(beanName, beanType, field.getDeclaringClass().getSimpleName(),
field.getName()));
}
}
@@ -301,8 +308,8 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
else if (candidateCount == 0) {
if (requireExistingBean) {
throw new IllegalStateException(
"Unable to override bean: there are no beans of type %s%s."
.formatted(beanType, requiredByField(field)));
"Unable to override bean: there are no beans of type %s (as required by field '%s.%s')."
.formatted(beanType, field.getDeclaringClass().getSimpleName(), field.getName()));
}
return null;
}
@@ -313,14 +320,14 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
}
throw new IllegalStateException(
"Unable to select a bean to override: found %d beans of type %s%s: %s"
.formatted(candidateCount, beanType, requiredByField(field), candidateNames));
"Unable to select a bean to override: found %d beans of type %s (as required by field '%s.%s'): %s"
.formatted(candidateCount, beanType, field.getDeclaringClass().getSimpleName(),
field.getName(), candidateNames));
}
private Set<String> getExistingBeanNamesByType(ConfigurableListableBeanFactory beanFactory, BeanOverrideHandler handler,
boolean checkAutowiredCandidate) {
Field field = handler.getField();
ResolvableType resolvableType = handler.getBeanType();
Class<?> type = resolvableType.toClass();
@@ -338,16 +345,16 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
}
// Filter out non-matching autowire candidates.
if (field != null && checkAutowiredCandidate) {
DependencyDescriptor descriptor = new DependencyDescriptor(field, true);
if (checkAutowiredCandidate) {
DependencyDescriptor descriptor = new DependencyDescriptor(handler.getField(), true);
beanNames.removeIf(beanName -> !beanFactory.isAutowireCandidate(beanName, descriptor));
}
// Filter out scoped proxy targets.
beanNames.removeIf(ScopedProxyUtils::isScopedTarget);
// In case of multiple matches, fall back on the field's name as a last resort.
if (field != null && beanNames.size() > 1) {
String fieldName = field.getName();
if (beanNames.size() > 1) {
String fieldName = handler.getField().getName();
if (beanNames.contains(fieldName)) {
return Set.of(fieldName);
}
@@ -445,19 +452,4 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
dlbf.destroySingleton(beanName);
}
private static String forField(@Nullable Field field) {
if (field == null) {
return "";
}
return " for field '%s.%s'".formatted(field.getDeclaringClass().getSimpleName(), field.getName());
}
private static String requiredByField(@Nullable Field field) {
if (field == null) {
return "";
}
return " (as required by field '%s.%s')".formatted(
field.getDeclaringClass().getSimpleName(), field.getName());
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -25,7 +25,7 @@ import org.springframework.test.context.MergedContextConfiguration;
/**
* {@link ContextCustomizer} implementation that registers the necessary
* infrastructure to support {@linkplain BeanOverride Bean Overrides}.
* infrastructure to support {@linkplain BeanOverride bean overriding}.
*
* @author Simon Baslé
* @author Stephane Nicoll
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -23,11 +23,12 @@ import java.util.Set;
import org.springframework.lang.Nullable;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizerFactory;
import org.springframework.test.context.TestContextAnnotationUtils;
import org.springframework.util.Assert;
/**
* {@link ContextCustomizerFactory} implementation that provides support for
* {@linkplain BeanOverride Bean Overrides}.
* Bean Overriding.
*
* @author Simon Baslé
* @author Stephane Nicoll
@@ -43,15 +44,18 @@ class BeanOverrideContextCustomizerFactory implements ContextCustomizerFactory {
List<ContextConfigurationAttributes> configAttributes) {
Set<BeanOverrideHandler> handlers = new LinkedHashSet<>();
findBeanOverrideHandlers(testClass, handlers);
findBeanOverrideHandler(testClass, handlers);
if (handlers.isEmpty()) {
return null;
}
return new BeanOverrideContextCustomizer(handlers);
}
private void findBeanOverrideHandlers(Class<?> testClass, Set<BeanOverrideHandler> handlers) {
BeanOverrideHandler.findAllHandlers(testClass).forEach(handler ->
private void findBeanOverrideHandler(Class<?> testClass, Set<BeanOverrideHandler> handlers) {
if (TestContextAnnotationUtils.searchEnclosingClass(testClass)) {
findBeanOverrideHandler(testClass.getEnclosingClass(), handlers);
}
BeanOverrideHandler.forTestClass(testClass).forEach(handler ->
Assert.state(handlers.add(handler), () ->
"Duplicate BeanOverrideHandler discovered in test class %s: %s"
.formatted(testClass.getName(), handler)));
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -17,18 +17,16 @@
package org.springframework.test.context.bean.override;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.config.BeanDefinition;
@@ -38,7 +36,6 @@ import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.style.ToStringCreator;
import org.springframework.lang.Nullable;
import org.springframework.test.context.TestContextAnnotationUtils;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
@@ -54,14 +51,12 @@ import static org.springframework.core.annotation.MergedAnnotations.SearchStrate
* unique set of metadata used to identify the bean to override. Overridden
* {@code equals()} and {@code hashCode()} methods should also delegate to the
* {@code super} implementations in this class in order to support the basic
* metadata used by all bean overrides. In addition, it is recommended that
* implementations override {@code toString()} to include all relevant metadata
* in order to enhance diagnostics.
* metadata used by all bean overrides.
*
* <p>Concrete implementations of {@code BeanOverrideHandler} can store additional
* metadata to use during override {@linkplain #createOverrideInstance instance
* creation} &mdash; for example, based on further processing of the annotation,
* the annotated field, or the annotated class.
* creation} &mdash; for example, based on further processing of the annotation
* or the annotated field.
*
* <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.
@@ -73,14 +68,9 @@ import static org.springframework.core.annotation.MergedAnnotations.SearchStrate
*/
public abstract class BeanOverrideHandler {
private static final Comparator<MergedAnnotation<? extends Annotation>> reversedMetaDistance =
Comparator.<MergedAnnotation<? extends Annotation>> comparingInt(MergedAnnotation::getDistance).reversed();
@Nullable
private final Field field;
private final Set<Annotation> qualifierAnnotations;
private final Set<Annotation> fieldAnnotations;
private final ResolvableType beanType;
@@ -90,11 +80,11 @@ public abstract class BeanOverrideHandler {
private final BeanOverrideStrategy strategy;
protected BeanOverrideHandler(@Nullable Field field, ResolvableType beanType, @Nullable String beanName,
protected BeanOverrideHandler(Field field, ResolvableType beanType, @Nullable String beanName,
BeanOverrideStrategy strategy) {
this.field = field;
this.qualifierAnnotations = getQualifierAnnotations(field);
this.fieldAnnotations = annotationSet(field);
this.beanType = beanType;
this.beanName = beanName;
this.strategy = strategy;
@@ -103,117 +93,39 @@ public abstract class BeanOverrideHandler {
/**
* Process the given {@code testClass} and build the corresponding
* {@code BeanOverrideHandler} list derived from {@link BeanOverride @BeanOverride}
* fields in the test class and its type hierarchy.
* <p>This method does not search the enclosing class hierarchy and does not
* search for {@code @BeanOverride} declarations on classes or interfaces.
* fields in the test class, its type hierarchy, and its enclosing class hierarchy.
* @param testClass the test class to process
* @return a list of bean override handlers
* @see #findAllHandlers(Class)
*/
public static List<BeanOverrideHandler> forTestClass(Class<?> testClass) {
return findHandlers(testClass, true);
}
/**
* Process the given {@code testClass} and build the corresponding
* {@code BeanOverrideHandler} list derived from {@link BeanOverride @BeanOverride}
* fields in the test class and in its type hierarchy as well as from
* {@code @BeanOverride} declarations on classes and interfaces.
* <p>This method additionally searches for {@code @BeanOverride} declarations
* in the enclosing class hierarchy based on
* {@link TestContextAnnotationUtils#searchEnclosingClass(Class)} semantics.
* @param testClass the test class to process
* @return a list of bean override handlers
* @since 6.2.2
*/
static List<BeanOverrideHandler> findAllHandlers(Class<?> testClass) {
return findHandlers(testClass, false);
}
private static List<BeanOverrideHandler> findHandlers(Class<?> testClass, boolean localFieldsOnly) {
List<BeanOverrideHandler> handlers = new ArrayList<>();
findHandlers(testClass, testClass, handlers, localFieldsOnly);
List<BeanOverrideHandler> handlers = new LinkedList<>();
ReflectionUtils.doWithFields(testClass, field -> processField(field, testClass, handlers));
return handlers;
}
/**
* Find handlers using tail recursion to ensure that "locally declared" bean overrides
* take precedence over inherited bean overrides.
* <p>Note: the search algorithm is effectively the inverse of the algorithm used in
* {@link org.springframework.test.context.TestContextAnnotationUtils#findAnnotationDescriptor(Class, Class)},
* but with tail recursion the semantics should be the same.
* @param clazz the class in/on which to search
* @param testClass the original test class
* @param handlers the list of handlers found
* @param localFieldsOnly whether to search only on local fields within the type hierarchy
* @since 6.2.2
*/
private static void findHandlers(Class<?> clazz, Class<?> testClass, List<BeanOverrideHandler> handlers,
boolean localFieldsOnly) {
// 1) Search enclosing class hierarchy.
if (!localFieldsOnly && TestContextAnnotationUtils.searchEnclosingClass(clazz)) {
findHandlers(clazz.getEnclosingClass(), testClass, handlers, localFieldsOnly);
}
// 2) Search class hierarchy.
Class<?> superclass = clazz.getSuperclass();
if (superclass != null && superclass != Object.class) {
findHandlers(superclass, testClass, handlers, localFieldsOnly);
}
if (!localFieldsOnly) {
// 3) Search interfaces.
for (Class<?> ifc : clazz.getInterfaces()) {
findHandlers(ifc, testClass, handlers, localFieldsOnly);
}
// 4) Process current class.
processClass(clazz, testClass, handlers);
}
// 5) Process fields in current class.
ReflectionUtils.doWithLocalFields(clazz, field -> processField(field, testClass, handlers));
}
private static void processClass(Class<?> clazz, Class<?> testClass, List<BeanOverrideHandler> handlers) {
processElement(clazz, testClass, (processor, composedAnnotation) ->
processor.createHandlers(composedAnnotation, testClass).forEach(handlers::add));
}
private static void processField(Field field, Class<?> testClass, List<BeanOverrideHandler> handlers) {
AtomicBoolean overrideAnnotationFound = new AtomicBoolean();
processElement(field, testClass, (processor, composedAnnotation) -> {
MergedAnnotations.from(field, DIRECT).stream(BeanOverride.class).forEach(mergedAnnotation -> {
Assert.state(!Modifier.isStatic(field.getModifiers()),
() -> "@BeanOverride field must not be static: " + field);
MergedAnnotation<?> metaSource = mergedAnnotation.getMetaSource();
Assert.state(metaSource != null, "@BeanOverride annotation must be meta-present");
BeanOverride beanOverride = mergedAnnotation.synthesize();
BeanOverrideProcessor processor = BeanUtils.instantiateClass(beanOverride.value());
Annotation composedAnnotation = metaSource.synthesize();
Assert.state(overrideAnnotationFound.compareAndSet(false, true),
() -> "Multiple @BeanOverride annotations found on field: " + field);
handlers.add(processor.createHandler(composedAnnotation, testClass, field));
BeanOverrideHandler handler = processor.createHandler(composedAnnotation, testClass, field);
handlers.add(handler);
});
}
private static void processElement(AnnotatedElement element, Class<?> testClass,
BiConsumer<BeanOverrideProcessor, Annotation> consumer) {
MergedAnnotations.from(element, DIRECT)
.stream(BeanOverride.class)
.sorted(reversedMetaDistance)
.forEach(mergedAnnotation -> {
MergedAnnotation<?> metaSource = mergedAnnotation.getMetaSource();
Assert.state(metaSource != null, "@BeanOverride annotation must be meta-present");
BeanOverride beanOverride = mergedAnnotation.synthesize();
BeanOverrideProcessor processor = BeanUtils.instantiateClass(beanOverride.value());
Annotation composedAnnotation = metaSource.synthesize();
consumer.accept(processor, composedAnnotation);
});
}
/**
* Get the annotated {@link Field}.
*/
@Nullable
public final Field getField() {
return this.field;
}
@@ -310,25 +222,20 @@ public abstract class BeanOverrideHandler {
!Objects.equals(this.strategy, that.strategy)) {
return false;
}
// by-name lookup
if (this.beanName != null) {
return true;
}
// by-type lookup
if (this.field == null) {
return (that.field == null);
}
return (that.field != null && this.field.getName().equals(that.field.getName()) &&
this.qualifierAnnotations.equals(that.qualifierAnnotations));
return (Objects.equals(this.field.getName(), that.field.getName()) &&
this.fieldAnnotations.equals(that.fieldAnnotations));
}
@Override
public int hashCode() {
int hash = Objects.hash(getClass(), this.beanType.getType(), this.beanName, this.strategy);
return (this.beanName != null ? hash : hash +
Objects.hash((this.field != null ? this.field.getName() : null), this.qualifierAnnotations));
Objects.hash(this.field.getName(), this.fieldAnnotations));
}
@Override
@@ -341,27 +248,9 @@ public abstract class BeanOverrideHandler {
.toString();
}
private static Set<Annotation> getQualifierAnnotations(@Nullable Field field) {
if (field == null) {
return Collections.emptySet();
}
Annotation[] candidates = field.getDeclaredAnnotations();
if (candidates.length == 0) {
return Collections.emptySet();
}
Set<Annotation> annotations = new HashSet<>(candidates.length - 1);
for (Annotation candidate : candidates) {
// Assume all non-BeanOverride annotations are "qualifiers".
if (!isBeanOverrideAnnotation(candidate.annotationType())) {
annotations.add(candidate);
}
}
return annotations;
}
private static boolean isBeanOverrideAnnotation(Class<? extends Annotation> type) {
return MergedAnnotations.from(type).isPresent(BeanOverride.class);
private static Set<Annotation> annotationSet(Field field) {
Annotation[] annotations = field.getAnnotations();
return (annotations.length != 0 ? new HashSet<>(Arrays.asList(annotations)) : Collections.emptySet());
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -18,13 +18,10 @@ package org.springframework.test.context.bean.override;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.List;
/**
* Strategy interface for Bean Override processing, which creates
* {@link BeanOverrideHandler} instances that drive how target beans are
* overridden.
* Strategy interface for Bean Override processing, which creates a
* {@link BeanOverrideHandler} that drives how the target bean is overridden.
*
* <p>At least one composed annotation that is meta-annotated with
* {@link BeanOverride @BeanOverride} must be a companion of this processor and
@@ -39,45 +36,17 @@ import java.util.List;
* @author Sam Brannen
* @since 6.2
*/
@FunctionalInterface
public interface BeanOverrideProcessor {
/**
* Create a {@link BeanOverrideHandler} for the given annotated field.
* <p>This method will only be invoked when a {@link BeanOverride @BeanOverride}
* annotation is declared on a field &mdash; for example, if the supplied field
* is annotated with {@code @MockitoBean}.
* @param overrideAnnotation the composed annotation that declares the
* {@code @BeanOverride} annotation which registers this processor
* {@link BeanOverride @BeanOverride} annotation which registers this processor
* @param testClass the test class to process
* @param field the annotated field
* @return the {@code BeanOverrideHandler} that should handle the given field
* @see #createHandlers(Annotation, Class)
*/
BeanOverrideHandler createHandler(Annotation overrideAnnotation, Class<?> testClass, Field field);
/**
* Create a list of {@link BeanOverrideHandler} instances for the given override
* annotation and test class.
* <p>This method will only be invoked when a {@link BeanOverride @BeanOverride}
* annotation is declared at the type level &mdash; for example, if the supplied
* test class is annotated with {@code @MockitoBean}.
* <p>Note that the test class may not be directly annotated with the override
* annotation. For example, the override annotation may have been declared
* on an interface, superclass, or enclosing class within the test class
* hierarchy.
* <p>The default implementation returns an empty list, signaling that this
* {@code BeanOverrideProcessor} does not support type-level {@code @BeanOverride}
* declarations. Can be overridden by concrete implementations to support
* type-level use cases.
* @param overrideAnnotation the composed annotation that declares the
* {@code @BeanOverride} annotation which registers this processor
* @param testClass the test class to process
* @return the list of {@code BeanOverrideHandlers} for the annotated class
* @since 6.2.2
* @see #createHandler(Annotation, Class, Field)
*/
default List<BeanOverrideHandler> createHandlers(Annotation overrideAnnotation, Class<?> testClass) {
return Collections.emptyList();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -108,11 +108,10 @@ class BeanOverrideRegistry {
}
void inject(Object target, BeanOverrideHandler handler) {
Field field = handler.getField();
Assert.notNull(field, () -> "BeanOverrideHandler must have a non-null field: " + handler);
String beanName = this.handlerToBeanNameMap.get(handler);
Assert.state(StringUtils.hasLength(beanName), () -> "No bean found for BeanOverrideHandler: " + handler);
inject(field, target, beanName);
Assert.state(StringUtils.hasLength(beanName),
() -> "No bean found for BeanOverrideHandler: " + handler);
inject(handler.getField(), target, beanName);
}
private void inject(Field field, Object target, String beanName) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -44,24 +44,24 @@ import org.springframework.test.context.bean.override.BeanOverride;
* you can set the {@link #enforceOverride() enforceOverride} attribute to {@code true}
* &mdash; for example, {@code @TestBean(enforceOverride = true)}.
*
* <p>The instance is created from a zero-argument static factory method whose
* return type is compatible with the annotated field. The factory method can be
* declared directly in the class which declares the {@code @TestBean} field or
* within the type hierarchy above that class, including implemented interfaces.
* If the {@code @TestBean} field is declared in a nested test class, the enclosing
* class hierarchy is also searched. Alternatively, a factory method in an external
* class can be referenced via its fully-qualified method name following the syntax
* {@code <fully-qualified class name>#<method name>} &mdash; for example,
* <p>The instance is created from a zero-argument static factory method in the
* test class whose return type is compatible with the annotated field. In the
* case of a nested test class, the enclosing class hierarchy is also searched.
* Similarly, if the test class extends from a base class or implements any
* interfaces, the entire type hierarchy is searched. Alternatively, a factory
* method in an external class can be referenced via its fully-qualified method
* name following the syntax {@code <fully-qualified class name>#<method name>}
* &mdash; for example,
* {@code @TestBean(methodName = "org.example.TestUtils#createCustomerRepository")}.
*
* <p>The factory method is deduced as follows.
*
* <ul>
* <li>If the {@link #methodName methodName} is specified, Spring looks for a static
* method with that name.</li>
* <li>If a method name is not specified, Spring looks for exactly one static method
* whose name is either the name of the annotated field or the {@link #name() name}
* of the bean (if specified).</li>
* <li>If the {@link #methodName()} is specified, look for a static method with
* that name.</li>
* <li>If a method name is not specified, look for exactly one static method
* named with either the name of the annotated field or the name of the bean
* (if specified).</li>
* </ul>
*
* <p>Consider the following example.
@@ -110,10 +110,6 @@ import org.springframework.test.context.bean.override.BeanOverride;
* {@code protected}, package-private (default visibility), or {@code private}
* depending on the needs or coding practices of the project.
*
* <p>{@code @TestBean} fields will be inherited from an enclosing test class by default. See
* {@link org.springframework.test.context.NestedTestConfiguration @NestedTestConfiguration}
* for details.
*
* @author Simon Baslé
* @author Stephane Nicoll
* @author Sam Brannen
@@ -150,17 +146,15 @@ public @interface TestBean {
/**
* Name of the static factory method that will be used to instantiate the bean
* to override.
* <p>A search will be performed to find the factory method in the class in
* which the {@code @TestBean} field is declared, in one of its superclasses,
* or in any implemented interfaces. If the {@code @TestBean} field is declared
* in a nested test class, the enclosing class hierarchy will also be searched.
* <p>A search will be performed to find the factory method in the test class,
* in one of its superclasses, or in any implemented interfaces. In the case
* of a nested test class, the enclosing class hierarchy will also be searched.
* <p>Alternatively, a factory method in an external class can be referenced
* via its fully-qualified method name following the syntax
* {@code <fully-qualified class name>#<method name>} &mdash; for example,
* {@code @TestBean(methodName = "org.example.TestUtils#createCustomerRepository")}.
* <p>If left unspecified, the name of the factory method will be detected
* based either on the name of the {@code @TestBean} field or the {@link #name() name}
* of the bean.
* based either on the name of the annotated field or the name of the bean.
*/
String methodName() default "";
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -67,7 +67,7 @@ class TestBeanOverrideProcessor implements BeanOverrideProcessor {
Method factoryMethod;
if (!methodName.isBlank()) {
// If the user specified an explicit method name, search for that.
factoryMethod = findTestBeanFactoryMethod(field.getDeclaringClass(), field.getType(), methodName);
factoryMethod = findTestBeanFactoryMethod(testClass, field.getType(), methodName);
}
else {
// Otherwise, search for candidate factory methods whose names match either
@@ -78,7 +78,7 @@ class TestBeanOverrideProcessor implements BeanOverrideProcessor {
if (beanName != null) {
candidateMethodNames.add(beanName);
}
factoryMethod = findTestBeanFactoryMethod(field.getDeclaringClass(), field.getType(), candidateMethodNames);
factoryMethod = findTestBeanFactoryMethod(testClass, field.getType(), candidateMethodNames);
}
return new TestBeanOverrideHandler(
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -38,7 +38,7 @@ abstract class AbstractMockitoBeanOverrideHandler extends BeanOverrideHandler {
private final MockReset reset;
protected AbstractMockitoBeanOverrideHandler(@Nullable Field field, ResolvableType beanType,
protected AbstractMockitoBeanOverrideHandler(Field field, ResolvableType beanType,
@Nullable String beanName, BeanOverrideStrategy strategy, @Nullable MockReset reset) {
super(field, beanType, beanName, strategy);
@@ -56,20 +56,20 @@ abstract class AbstractMockitoBeanOverrideHandler extends BeanOverrideHandler {
@Override
protected void trackOverrideInstance(Object mock, SingletonBeanRegistry trackingBeanRegistry) {
getMockBeans(trackingBeanRegistry).add(mock);
getMockitoBeans(trackingBeanRegistry).add(mock);
}
private static MockBeans getMockBeans(SingletonBeanRegistry trackingBeanRegistry) {
String beanName = MockBeans.class.getName();
MockBeans mockBeans = null;
private static MockitoBeans getMockitoBeans(SingletonBeanRegistry trackingBeanRegistry) {
String beanName = MockitoBeans.class.getName();
MockitoBeans mockitoBeans = null;
if (trackingBeanRegistry.containsSingleton(beanName)) {
mockBeans = (MockBeans) trackingBeanRegistry.getSingleton(beanName);
mockitoBeans = (MockitoBeans) trackingBeanRegistry.getSingleton(beanName);
}
if (mockBeans == null) {
mockBeans = new MockBeans();
trackingBeanRegistry.registerSingleton(beanName, mockBeans);
if (mockitoBeans == null) {
mockitoBeans = new MockitoBeans();
trackingBeanRegistry.registerSingleton(beanName, mockitoBeans);
}
return mockBeans;
return mockitoBeans;
}
@Override
@@ -1,54 +0,0 @@
/*
* Copyright 2002-2025 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;
import java.util.ArrayList;
import java.util.List;
import org.mockito.Mockito;
/**
* Beans created using Mockito.
*
* @author Andy Wilkinson
* @author Sam Brannen
* @since 6.2
*/
class MockBeans {
private final List<Object> beans = new ArrayList<>();
void add(Object bean) {
this.beans.add(bean);
}
/**
* Reset all Mockito beans configured with the supplied {@link MockReset} strategy.
* <p>No mocks will be reset if the supplied strategy is {@link MockReset#NONE}.
*/
void resetAll(MockReset reset) {
if (reset != MockReset.NONE) {
for (Object bean : this.beans) {
if (reset == MockReset.get(bean)) {
Mockito.reset(bean);
}
}
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -18,7 +18,6 @@ package org.springframework.test.context.bean.override.mockito;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@@ -30,37 +29,17 @@ import org.springframework.core.annotation.AliasFor;
import org.springframework.test.context.bean.override.BeanOverride;
/**
* {@code @MockitoBean} is an annotation that can be used in test classes to
* override beans in a test's
* {@code @MockitoBean} is an annotation that can be applied to a non-static field
* in a test class to override a bean in the test's
* {@link org.springframework.context.ApplicationContext ApplicationContext}
* using Mockito mocks.
* using a Mockito mock.
*
* <p>{@code @MockitoBean} can be applied in the following ways.
* <ul>
* <li>On a non-static field in a test class or any of its superclasses.</li>
* <li>On a non-static field in an enclosing class for a {@code @Nested} test class
* or in any class in the type hierarchy or enclosing class hierarchy above the
* {@code @Nested} test class.</li>
* <li>At the type level on a test class or any superclass or implemented interface
* in the type hierarchy above the test class.</li>
* <li>At the type level on an enclosing class for a {@code @Nested} test class
* or on any class or interface in the type hierarchy or enclosing class hierarchy
* above the {@code @Nested} test class.</li>
* </ul>
*
* <p>When {@code @MockitoBean} is declared on a field, the bean to mock is inferred
* from the type of the annotated field. If multiple candidates exist, a
* {@code @Qualifier} annotation can be declared on the field 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 mock by setting the {@link #value() value} or
* {@link #name() name} attribute.
*
* <p>When {@code @MockitoBean} is declared at the type level, the type of bean
* to mock must be supplied via the {@link #types() types} attribute. If multiple
* candidates exist, you can explicitly specify a bean name to mock by setting the
* {@link #name() name} attribute. Note, however, that the {@code types} attribute
* must contain a single type if an explicit bean {@code name} is configured.
* <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 mock by setting the
* {@link #value() value} or {@link #name() name} attribute.
*
* <p>A bean will be created if a corresponding bean does not exist. However, if
* you would like for the test to fail when a corresponding bean does not exist,
@@ -84,29 +63,15 @@ import org.springframework.test.context.bean.override.BeanOverride;
* (default visibility), or {@code private} depending on the needs or coding
* practices of the project.
*
* <p>{@code @MockitoBean} fields and type-level {@code @MockitoBean} declarations
* will be inherited from an enclosing test class by default. See
* {@link org.springframework.test.context.NestedTestConfiguration @NestedTestConfiguration}
* for details.
*
* <p>{@code @MockitoBean} may be used as a <em>meta-annotation</em> to create custom
* <em>composed annotations</em> &mdash; for example, to define common mock
* configuration in a single annotation that can be reused across a test suite.
* {@code @MockitoBean} can also be used as a <em>{@linkplain Repeatable repeatable}</em>
* annotation at the type level &mdash; for example, to mock several beans by
* {@link #name() name}.
*
* @author Simon Baslé
* @author Sam Brannen
* @since 6.2
* @see org.springframework.test.context.bean.override.mockito.MockitoBeans @MockitoBeans
* @see org.springframework.test.context.bean.override.mockito.MockitoSpyBean @MockitoSpyBean
* @see org.springframework.test.context.bean.override.convention.TestBean @TestBean
*/
@Target({ElementType.FIELD, ElementType.TYPE})
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(MockitoBeans.class)
@BeanOverride(MockitoBeanOverrideProcessor.class)
public @interface MockitoBean {
@@ -122,27 +87,13 @@ public @interface MockitoBean {
/**
* Name of the bean to mock.
* <p>If left unspecified, the bean to mock is selected according to the
* configured {@link #types() types} or the annotated field's type, taking
* qualifiers into account if necessary. See the {@linkplain MockitoBean
* class-level documentation} for details.
* 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 "";
/**
* One or more types to mock.
* <p>Defaults to none.
* <p>Each type specified will result in a mock being created and registered
* with the {@code ApplicationContext}.
* <p>Types must be omitted when the annotation is used on a field.
* <p>When {@code @MockitoBean} also defines a {@link #name}, this attribute
* can only contain a single value.
* @return the types to mock
* @since 6.2.2
*/
Class<?>[] types() default {};
/**
* Extra interfaces that should also be declared by the mock.
* <p>Defaults to none.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -57,24 +57,20 @@ class MockitoBeanOverrideHandler extends AbstractMockitoBeanOverrideHandler {
private final boolean serializable;
MockitoBeanOverrideHandler(ResolvableType typeToMock, MockitoBean mockitoBean) {
this(null, typeToMock, mockitoBean);
}
MockitoBeanOverrideHandler(@Nullable Field field, ResolvableType typeToMock, MockitoBean mockitoBean) {
MockitoBeanOverrideHandler(Field field, ResolvableType typeToMock, MockitoBean mockitoBean) {
this(field, typeToMock, (!mockitoBean.name().isBlank() ? mockitoBean.name() : null),
(mockitoBean.enforceOverride() ? REPLACE : REPLACE_OR_CREATE),
mockitoBean.reset(), mockitoBean.extraInterfaces(), mockitoBean.answers(), mockitoBean.serializable());
}
private MockitoBeanOverrideHandler(@Nullable Field field, ResolvableType typeToMock, @Nullable String beanName,
BeanOverrideStrategy strategy, MockReset reset, Class<?>[] extraInterfaces, Answers answers,
private MockitoBeanOverrideHandler(Field field, ResolvableType typeToMock, @Nullable String beanName,
BeanOverrideStrategy strategy, MockReset reset, Class<?>[] extraInterfaces, @Nullable Answers answers,
boolean serializable) {
super(field, typeToMock, beanName, strategy, reset);
Assert.notNull(typeToMock, "'typeToMock' must not be null");
this.extraInterfaces = asClassSet(extraInterfaces);
this.answers = answers;
this.answers = (answers != null ? answers : Answers.RETURNS_DEFAULTS);
this.serializable = serializable;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -18,20 +18,15 @@ package org.springframework.test.context.bean.override.mockito;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import org.springframework.core.ResolvableType;
import org.springframework.test.context.bean.override.BeanOverrideHandler;
import org.springframework.test.context.bean.override.BeanOverrideProcessor;
import org.springframework.util.Assert;
/**
* {@link BeanOverrideProcessor} implementation that provides support for
* {@link MockitoBean @MockitoBean} and {@link MockitoSpyBean @MockitoSpyBean}.
*
* @author Simon Baslé
* @author Sam Brannen
* @since 6.2
* @see MockitoBean @MockitoBean
* @see MockitoSpyBean @MockitoSpyBean
@@ -40,10 +35,8 @@ class MockitoBeanOverrideProcessor implements BeanOverrideProcessor {
@Override
public AbstractMockitoBeanOverrideHandler createHandler(Annotation overrideAnnotation, Class<?> testClass, Field field) {
if (overrideAnnotation instanceof MockitoBean mockitoBean) {
Assert.state(mockitoBean.types().length == 0,
"The @MockitoBean 'types' attribute must be omitted when declared on a field");
return new MockitoBeanOverrideHandler(field, ResolvableType.forField(field, testClass), mockitoBean);
if (overrideAnnotation instanceof MockitoBean mockBean) {
return new MockitoBeanOverrideHandler(field, ResolvableType.forField(field, testClass), mockBean);
}
else if (overrideAnnotation instanceof MockitoSpyBean spyBean) {
return new MockitoSpyBeanOverrideHandler(field, ResolvableType.forField(field, testClass), spyBean);
@@ -54,23 +47,4 @@ class MockitoBeanOverrideProcessor implements BeanOverrideProcessor {
.formatted(field.getDeclaringClass().getName(), field.getName()));
}
@Override
public List<BeanOverrideHandler> createHandlers(Annotation overrideAnnotation, Class<?> testClass) {
if (!(overrideAnnotation instanceof MockitoBean mockitoBean)) {
throw new IllegalStateException("""
Invalid annotation passed to MockitoBeanOverrideProcessor: \
expected @MockitoBean on test class """ + testClass.getName());
}
Class<?>[] types = mockitoBean.types();
Assert.state(types.length > 0,
"The @MockitoBean 'types' attribute must not be empty when declared on a class");
Assert.state(mockitoBean.name().isEmpty() || types.length == 1,
"The @MockitoBean 'name' attribute cannot be used when mocking multiple types");
List<BeanOverrideHandler> handlers = new ArrayList<>();
for (Class<?> type : types) {
handlers.add(new MockitoBeanOverrideHandler(ResolvableType.forClass(type), mockitoBean));
}
return handlers;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -16,26 +16,39 @@
package org.springframework.test.context.bean.override.mockito;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.List;
import org.mockito.Mockito;
/**
* Container for {@link MockitoBean @MockitoBean} annotations which allows
* {@code @MockitoBean} to be used as a {@linkplain java.lang.annotation.Repeatable
* repeatable annotation} at the type level &mdash; for example, on test classes
* or interfaces implemented by test classes.
* Beans created using Mockito.
*
* @author Andy Wilkinson
* @author Sam Brannen
* @since 6.2.2
* @since 6.2
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MockitoBeans {
class MockitoBeans {
MockitoBean[] value();
private final List<Object> beans = new ArrayList<>();
void add(Object bean) {
this.beans.add(bean);
}
/**
* Reset all Mockito beans configured with the supplied {@link MockReset} strategy.
* <p>No mocks will be reset if the supplied strategy is {@link MockReset#NONE}.
*/
void resetAll(MockReset reset) {
if (reset != MockReset.NONE) {
for (Object bean : this.beans) {
if (reset == MockReset.get(bean)) {
Mockito.reset(bean);
}
}
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -116,7 +116,7 @@ public class MockitoResetTestExecutionListener extends AbstractTestExecutionList
}
}
try {
beanFactory.getBean(MockBeans.class).resetAll(reset);
beanFactory.getBean(MockitoBeans.class).resetAll(reset);
}
catch (NoSuchBeanDefinitionException ex) {
// Continue
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -56,10 +56,6 @@ import org.springframework.test.context.bean.override.BeanOverride;
* (default visibility), or {@code private} depending on the needs or coding
* practices of the project.
*
* <p>{@code @MockitoSpyBean} fields will be inherited from an enclosing test class by default.
* See {@link org.springframework.test.context.NestedTestConfiguration @NestedTestConfiguration}
* for details.
*
* @author Simon Baslé
* @author Sam Brannen
* @since 6.2
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -48,9 +48,15 @@ class MockitoSpyBeanOverrideHandler extends AbstractMockitoBeanOverrideHandler {
new SpringAopBypassingVerificationStartedListener();
MockitoSpyBeanOverrideHandler(Field field, ResolvableType typeToSpy, MockitoSpyBean spyBean) {
super(field, typeToSpy, (StringUtils.hasText(spyBean.name()) ? spyBean.name() : null),
BeanOverrideStrategy.WRAP, spyBean.reset());
MockitoSpyBeanOverrideHandler(Field field, ResolvableType typeToSpy, MockitoSpyBean spyAnnotation) {
this(field, typeToSpy, (StringUtils.hasText(spyAnnotation.name()) ? spyAnnotation.name() : null),
spyAnnotation.reset());
}
MockitoSpyBeanOverrideHandler(Field field, ResolvableType typeToSpy, @Nullable String beanName,
MockReset reset) {
super(field, typeToSpy, beanName, BeanOverrideStrategy.WRAP, reset);
Assert.notNull(typeToSpy, "typeToSpy must not be null");
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -78,21 +78,8 @@ public class HttpHeadersAssert extends AbstractMapAssert<HttpHeadersAssert, Http
* Verify that the actual HTTP headers do not contain any of the headers
* with the given {@code names}.
* @param names the names of HTTP headers that should not be present
* @since 6.2.2
* @see #doesNotContainKeys
*/
public HttpHeadersAssert doesNotContainHeaders(String... names) {
return doesNotContainKeys(names);
}
/**
* Verify that the actual HTTP headers do not contain any of the headers
* with the given {@code names}.
* @param names the names of HTTP headers that should not be present
* @see #doesNotContainKeys
* @deprecated in favor of {@link #doesNotContainHeaders(String...)}
*/
@Deprecated(since = "6.2.2", forRemoval = true)
public HttpHeadersAssert doesNotContainsHeaders(String... names) {
return doesNotContainKeys(names);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -395,7 +395,7 @@ class BeanOverrideBeanFactoryPostProcessorTests {
private static AnnotationConfigApplicationContext createContext(Class<?> testClass) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
Set<BeanOverrideHandler> handlers = new LinkedHashSet<>(BeanOverrideTestUtils.findHandlers(testClass));
Set<BeanOverrideHandler> handlers = new LinkedHashSet<>(BeanOverrideHandler.forTestClass(testClass));
new BeanOverrideContextCustomizer(handlers).customizeContext(context, mock(MergedContextConfiguration.class));
return context;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -51,14 +51,14 @@ class BeanOverrideHandlerTests {
@Test
void forTestClassWithSingleField() {
List<BeanOverrideHandler> handlers = BeanOverrideTestUtils.findHandlers(SingleAnnotation.class);
List<BeanOverrideHandler> handlers = BeanOverrideHandler.forTestClass(SingleAnnotation.class);
assertThat(handlers).singleElement().satisfies(hasBeanOverrideHandler(
field(SingleAnnotation.class, "message"), String.class, null));
}
@Test
void forTestClassWithMultipleFields() {
List<BeanOverrideHandler> handlers = BeanOverrideTestUtils.findHandlers(MultipleAnnotations.class);
List<BeanOverrideHandler> handlers = BeanOverrideHandler.forTestClass(MultipleAnnotations.class);
assertThat(handlers).hasSize(2)
.anySatisfy(hasBeanOverrideHandler(
field(MultipleAnnotations.class, "message"), String.class, null))
@@ -68,7 +68,7 @@ class BeanOverrideHandlerTests {
@Test
void forTestClassWithMultipleFieldsWithIdenticalMetadata() {
List<BeanOverrideHandler> handlers = BeanOverrideTestUtils.findHandlers(MultipleAnnotationsDuplicate.class);
List<BeanOverrideHandler> handlers = BeanOverrideHandler.forTestClass(MultipleAnnotationsDuplicate.class);
assertThat(handlers).hasSize(2)
.anySatisfy(hasBeanOverrideHandler(
field(MultipleAnnotationsDuplicate.class, "message1"), String.class, "messageBean"))
@@ -81,7 +81,7 @@ class BeanOverrideHandlerTests {
void forTestClassWithCompetingBeanOverrideAnnotationsOnSameField() {
Field faultyField = field(MultipleAnnotationsOnSameField.class, "message");
assertThatIllegalStateException()
.isThrownBy(() -> BeanOverrideTestUtils.findHandlers(MultipleAnnotationsOnSameField.class))
.isThrownBy(() -> BeanOverrideHandler.forTestClass(MultipleAnnotationsOnSameField.class))
.withMessageStartingWith("Multiple @BeanOverride annotations found")
.withMessageContaining(faultyField.toString());
}
@@ -90,7 +90,7 @@ class BeanOverrideHandlerTests {
void forTestClassWithStaticBeanOverrideField() {
Field staticField = field(StaticBeanOverrideField.class, "message");
assertThatIllegalStateException()
.isThrownBy(() -> BeanOverrideTestUtils.findHandlers(StaticBeanOverrideField.class))
.isThrownBy(() -> BeanOverrideHandler.forTestClass(StaticBeanOverrideField.class))
.withMessage("@BeanOverride field must not be static: " + staticField);
}
@@ -1,37 +0,0 @@
/*
* Copyright 2002-2025 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;
import java.util.List;
/**
* Test utilities for Bean Overrides.
*
* @author Sam Brannen
* @since 6.2.2
*/
public abstract class BeanOverrideTestUtils {
public static List<BeanOverrideHandler> findHandlers(Class<?> testClass) {
return BeanOverrideHandler.forTestClass(testClass);
}
public static List<BeanOverrideHandler> findAllHandlers(Class<?> testClass) {
return BeanOverrideHandler.findAllHandlers(testClass);
}
}
@@ -0,0 +1,173 @@
/*
* 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.convention;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.bean.override.convention.TestBeanForInheritanceIntegrationTests.AbstractTestBeanIntegrationTestCase.FakePojo;
import org.springframework.test.context.bean.override.convention.TestBeanForInheritanceIntegrationTests.AbstractTestBeanIntegrationTestCase.Pojo;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for {@link TestBean} that use inheritance.
*
* <p>Tests inheritance within a class hierarchy as well as "inheritance" within
* an enclosing class hierarchy.
*
* @author Simon Baslé
* @author Sam Brannen
* @since 6.2
*/
public class TestBeanForInheritanceIntegrationTests {
static Pojo enclosingClassBeanOverride() {
return new FakePojo("in enclosing test class");
}
@SpringJUnitConfig
public abstract static class AbstractTestBeanIntegrationTestCase {
@TestBean(name = "someBean")
Pojo someBean;
@TestBean(name = "otherBean")
Pojo otherBean;
@TestBean(name = "thirdBean")
Pojo anotherBean;
static Pojo otherBean() {
return new FakePojo("otherBean in superclass");
}
static Pojo thirdBean() {
return new FakePojo("third in superclass");
}
static Pojo commonBeanOverride() {
return new FakePojo("in superclass");
}
public interface Pojo {
default String getValue() {
return "Prod";
}
}
static class ProdPojo implements Pojo { }
static class FakePojo implements Pojo {
final String value;
protected FakePojo(String value) {
this.value = value;
}
@Override
public String getValue() {
return this.value;
}
@Override
public String toString() {
return getValue();
}
}
@Configuration(proxyBeanMethods = false)
static class Config {
@Bean
Pojo someBean() {
return new ProdPojo();
}
@Bean
Pojo otherBean() {
return new ProdPojo();
}
@Bean
Pojo thirdBean() {
return new ProdPojo();
}
@Bean
Pojo pojo() {
return new ProdPojo();
}
@Bean
Pojo pojo2() {
return new ProdPojo();
}
}
}
@Nested
@DisplayName("Nested, concrete inherited tests with correct @TestBean setup")
class NestedConcreteTestBeanIntegrationTests extends AbstractTestBeanIntegrationTestCase {
@Autowired
ApplicationContext ctx;
@TestBean(name = "pojo", methodName = "commonBeanOverride")
Pojo pojo;
@TestBean(name = "pojo2", methodName = "enclosingClassBeanOverride")
Pojo pojo2;
static Pojo someBean() {
return new FakePojo("someBeanOverride");
}
// Hides otherBean() defined in AbstractTestBeanIntegrationTestCase.
static Pojo otherBean() {
return new FakePojo("otherBean in subclass");
}
@Test
void fieldInSubtypeWithFactoryMethodInSupertype() {
assertThat(ctx.getBean("pojo")).as("applicationContext").hasToString("in superclass");
assertThat(this.pojo.getValue()).as("injection point").isEqualTo("in superclass");
}
@Test
void fieldInSupertypeWithFactoryMethodInSubtype() {
assertThat(ctx.getBean("someBean")).as("applicationContext").hasToString("someBeanOverride");
assertThat(this.someBean.getValue()).as("injection point").isEqualTo("someBeanOverride");
}
@Test
void fieldInSupertypeWithPrioritizedFactoryMethodInSubtype() {
assertThat(ctx.getBean("otherBean")).as("applicationContext").hasToString("otherBean in subclass");
assertThat(super.otherBean.getValue()).as("injection point").isEqualTo("otherBean in subclass");
}
@Test
void fieldInNestedClassWithFactoryMethodInEnclosingClass() {
assertThat(ctx.getBean("pojo2")).as("applicationContext").hasToString("in enclosing test class");
assertThat(this.pojo2.getValue()).as("injection point").isEqualTo("in enclosing test class");
}
}
}
@@ -1,213 +0,0 @@
/*
* Copyright 2002-2025 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.convention;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for {@link TestBean} that use inheritance.
*
* <p>Tests inheritance within a class hierarchy as well as "inheritance" within
* an enclosing class hierarchy.
*
* @author Simon Baslé
* @author Sam Brannen
* @since 6.2
*/
@SpringJUnitConfig
public class TestBeanInheritanceIntegrationTests {
@TestBean
Pojo puzzleBean;
static Pojo puzzleBean() {
return new FakePojo("puzzle in enclosing class");
}
static Pojo enclosingClassFactoryMethod() {
return new FakePojo("in enclosing test class");
}
abstract static class AbstractTestCase {
@TestBean("otherBean")
Pojo otherBean;
@TestBean
Pojo anotherBean;
@TestBean
Pojo enigmaBean;
static Pojo otherBean() {
return new FakePojo("other in superclass");
}
static Pojo anotherBean() {
return new FakePojo("another in superclass");
}
static Pojo enigmaBean() {
return new FakePojo("enigma in superclass");
}
static Pojo commonBean() {
return new FakePojo("common in superclass");
}
}
@Nested
@DisplayName("Nested, concrete inherited tests with correct @TestBean setup")
class NestedTests extends AbstractTestCase {
@Autowired
ApplicationContext ctx;
@TestBean(methodName = "commonBean")
Pojo pojo;
@TestBean(name = "pojo2", methodName = "enclosingClassFactoryMethod")
Pojo pojo2;
@TestBean
Pojo enigmaBean;
@TestBean
Pojo puzzleBean;
// "Overrides" puzzleBean() defined in TestBeanInheritanceIntegrationTests.
static Pojo puzzleBean() {
return new FakePojo("puzzle in nested class");
}
// "Overrides" enigmaBean() defined in AbstractTestCase.
static Pojo enigmaBean() {
return new FakePojo("enigma in subclass");
}
static Pojo otherBean() {
return new FakePojo("other in subclass");
}
@Test
void fieldInSuperclassWithFactoryMethodInSuperclass() {
assertThat(ctx.getBean("anotherBean")).as("applicationContext").hasToString("another in superclass");
assertThat(super.anotherBean.value()).as("injection point").isEqualTo("another in superclass");
}
@Test // gh-34204
void fieldInSuperclassWithFactoryMethodInSuperclassAndInSubclass() {
// We do not expect "other in subclass", because the @TestBean declaration in
// AbstractTestCase cannot "see" the otherBean() factory method in the subclass.
assertThat(ctx.getBean("otherBean")).as("applicationContext").hasToString("other in superclass");
assertThat(super.otherBean.value()).as("injection point").isEqualTo("other in superclass");
}
@Test
void fieldInSubclassWithFactoryMethodInSuperclass() {
assertThat(ctx.getBean("pojo")).as("applicationContext").hasToString("common in superclass");
assertThat(this.pojo.value()).as("injection point").isEqualTo("common in superclass");
}
@Test
void fieldInNestedClassWithFactoryMethodInEnclosingClass() {
assertThat(ctx.getBean("pojo2")).as("applicationContext").hasToString("in enclosing test class");
assertThat(this.pojo2.value()).as("injection point").isEqualTo("in enclosing test class");
}
@Test // gh-34194, gh-34204
void testBeanInSubclassOverridesTestBeanInSuperclass() {
assertThat(ctx.getBean("enigmaBean")).as("applicationContext").hasToString("enigma in subclass");
assertThat(this.enigmaBean.value()).as("injection point").isEqualTo("enigma in subclass");
}
@Test // gh-34194, gh-34204
void testBeanInNestedClassOverridesTestBeanInEnclosingClass() {
assertThat(ctx.getBean("puzzleBean")).as("applicationContext").hasToString("puzzle in nested class");
assertThat(this.puzzleBean.value()).as("injection point").isEqualTo("puzzle in nested class");
}
}
@Configuration(proxyBeanMethods = false)
static class Config {
@Bean
Pojo otherBean() {
return new ProdPojo();
}
@Bean
Pojo anotherBean() {
return new ProdPojo();
}
@Bean
Pojo enigmaBean() {
return new ProdPojo();
}
@Bean
Pojo puzzleBean() {
return new ProdPojo();
}
@Bean
Pojo pojo() {
return new ProdPojo();
}
@Bean
Pojo pojo2() {
return new ProdPojo();
}
}
interface Pojo {
default String value() {
return "prod";
}
}
static class ProdPojo implements Pojo {
@Override
public String toString() {
return value();
}
}
record FakePojo(String value) implements Pojo {
@Override
public String toString() {
return value();
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -26,7 +26,6 @@ import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.ResolvableType;
import org.springframework.test.context.bean.override.BeanOverrideHandler;
import org.springframework.test.context.bean.override.BeanOverrideStrategy;
import org.springframework.test.context.bean.override.BeanOverrideTestUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
@@ -43,21 +42,21 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
class TestBeanOverrideHandlerTests {
@Test
void beanNameIsSetToNullIfAnnotationNameIsEmpty() {
List<BeanOverrideHandler> handlers = BeanOverrideTestUtils.findHandlers(SampleOneOverride.class);
void setsBeanNameToNullIfAnnotationNameIsNull() {
List<BeanOverrideHandler> handlers = BeanOverrideHandler.forTestClass(SampleOneOverride.class);
assertThat(handlers).singleElement().extracting(BeanOverrideHandler::getBeanName).isNull();
}
@Test
void beanNameIsSetToAnnotationName() {
List<BeanOverrideHandler> handlers = BeanOverrideTestUtils.findHandlers(SampleOneOverrideWithName.class);
void setsBeanNameToAnnotationName() {
List<BeanOverrideHandler> handlers = BeanOverrideHandler.forTestClass(SampleOneOverrideWithName.class);
assertThat(handlers).singleElement().extracting(BeanOverrideHandler::getBeanName).isEqualTo("anotherBean");
}
@Test
void failsWithMissingMethod() {
assertThatIllegalStateException()
.isThrownBy(() -> BeanOverrideTestUtils.findHandlers(SampleMissingMethod.class))
.isThrownBy(() -> BeanOverrideHandler.forTestClass(SampleMissingMethod.class))
.withMessage("No static method found named message() in %s with return type %s",
SampleMissingMethod.class.getName(), String.class.getName());
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -26,10 +26,9 @@ import org.springframework.test.context.bean.override.BeanOverrideContextCustomi
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Tests for {@link TestBean @TestBean}.
* Tests for {@link TestBean}.
*
* @author Stephane Nicoll
* @author Sam Brannen
*/
public class TestBeanTests {
@@ -65,8 +64,8 @@ public class TestBeanTests {
.isThrownBy(context::refresh)
.withMessage("""
Unable to override bean: there are no beans of \
type %s (as required by field '%s.example').""",
String.class.getName(), FailureByTypeLookup.class.getSimpleName());
type %s (as required by field '%s.example').""".formatted(
String.class.getName(), FailureByTypeLookup.class.getSimpleName()));
}
@Test
@@ -88,8 +87,8 @@ public class TestBeanTests {
assertThatIllegalStateException()
.isThrownBy(() -> BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(
FailureMissingDefaultOverrideMethod.class, context))
.withMessage("No static method found named example() or beanToOverride() in %s with return type %s",
FailureMissingDefaultOverrideMethod.class.getName(), String.class.getName());
.withMessage("No static method found named example() or beanToOverride() in %s with return type %s"
.formatted(FailureMissingDefaultOverrideMethod.class.getName(), String.class.getName()));
}
@Test
@@ -98,8 +97,8 @@ public class TestBeanTests {
assertThatIllegalStateException()
.isThrownBy(() -> BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(
FailureMissingExplicitOverrideMethod.class, context))
.withMessage("No static method found named createExample() in %s with return type %s",
FailureMissingExplicitOverrideMethod.class.getName(), String.class.getName());
.withMessage("No static method found named createExample() in %s with return type %s"
.formatted(FailureMissingExplicitOverrideMethod.class.getName(), String.class.getName()));
}
@Test
@@ -109,19 +108,19 @@ public class TestBeanTests {
assertThatIllegalStateException()
.isThrownBy(() -> BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(
FailureOverrideInParentWithoutFactoryMethod.class, context))
.withMessage("No static method found named beanToOverride() in %s with return type %s",
AbstractByNameLookup.class.getName(), String.class.getName());
.withMessage("No static method found named beanToOverride() in %s with return type %s"
.formatted(FailureOverrideInParentWithoutFactoryMethod.class.getName(), String.class.getName()));
}
@Test
void contextCustomizerCannotBeCreatedWithCompetingOverrideMethods() {
void contextCustomizerCannotBeCreatedWitCompetingOverrideMethods() {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("bean", String.class, () -> "example");
assertThatIllegalStateException()
.isThrownBy(() -> BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(
FailureCompetingOverrideMethods.class, context))
.withMessage("Found 2 competing static methods named example() or beanToOverride() in %s with return type %s",
FailureCompetingOverrideMethods.class.getName(), String.class.getName());
.withMessage("Found 2 competing static methods named example() or beanToOverride() in %s with return type %s"
.formatted(FailureCompetingOverrideMethods.class.getName(), String.class.getName()));
}
@@ -150,7 +149,9 @@ public class TestBeanTests {
@TestBean(name = "beanToOverride")
private String example;
// No example() or beanToOverride() method
// Expected static String example() { ... }
// or static String beanToOverride() { ... }
}
static class FailureMissingExplicitOverrideMethod {
@@ -158,22 +159,28 @@ public class TestBeanTests {
@TestBean(methodName = "createExample")
private String example;
// NO createExample() method
// Expected static String createExample() { ... }
}
abstract static class AbstractByNameLookup {
@TestBean
String beanToOverride;
@TestBean(methodName = "beanToOverride")
protected String beanToOverride;
// No beanToOverride() method
}
static class FailureOverrideInParentWithoutFactoryMethod extends AbstractByNameLookup {
// No beanToOverride() method
}
abstract static class AbstractCompetingMethods {
@TestBean(name = "beanToOverride")
protected String example;
static String example() {
throw new IllegalStateException("Should not be called");
}
@@ -181,12 +188,10 @@ public class TestBeanTests {
static class FailureCompetingOverrideMethods extends AbstractCompetingMethods {
@TestBean(name = "beanToOverride")
String example;
static String beanToOverride() {
throw new IllegalStateException("Should not be called");
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -26,7 +26,6 @@ import org.mockito.Answers;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.test.context.bean.override.BeanOverrideHandler;
import org.springframework.test.context.bean.override.BeanOverrideTestUtils;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -35,179 +34,104 @@ import static org.assertj.core.api.Assertions.assertThat;
* Tests for {@link MockitoBeanOverrideHandler}.
*
* @author Stephane Nicoll
* @author Sam Brannen
* @since 6.2
*/
class MockitoBeanOverrideHandlerTests {
@Test
void beanNameIsSetToNullIfAnnotationNameIsEmpty() {
List<BeanOverrideHandler> list = BeanOverrideTestUtils.findHandlers(SampleOneMock.class);
void forTestClassSetsNameToNullIfAnnotationNameIsNull() {
List<BeanOverrideHandler> list = BeanOverrideHandler.forTestClass(SampleOneMock.class);
assertThat(list).singleElement().satisfies(handler -> assertThat(handler.getBeanName()).isNull());
}
@Test
void beanNameIsSetToAnnotationName() {
List<BeanOverrideHandler> list = BeanOverrideTestUtils.findHandlers(SampleOneMockWithName.class);
void forTestClassSetsNameToAnnotationName() {
List<BeanOverrideHandler> list = BeanOverrideHandler.forTestClass(SampleOneMockWithName.class);
assertThat(list).singleElement().satisfies(handler -> assertThat(handler.getBeanName()).isEqualTo("anotherService"));
}
@Test
void isEqualToWithSameInstanceFromField() {
MockitoBeanOverrideHandler handler = createHandler(sampleField("service"));
void isEqualToWithSameInstance() {
MockitoBeanOverrideHandler handler = createBeanOverrideHandler(sampleField("service"));
assertThat(handler).isEqualTo(handler);
assertThat(handler).hasSameHashCodeAs(handler);
}
@Test
void isEqualToWithSameMetadataFromField() {
MockitoBeanOverrideHandler handler1 = createHandler(sampleField("service"));
MockitoBeanOverrideHandler handler2 = createHandler(sampleField("service"));
void isEqualToWithSameMetadata() {
MockitoBeanOverrideHandler handler1 = createBeanOverrideHandler(sampleField("service"));
MockitoBeanOverrideHandler handler2 = createBeanOverrideHandler(sampleField("service"));
assertThat(handler1).isEqualTo(handler2);
assertThat(handler1).hasSameHashCodeAs(handler2);
}
@Test // gh-33925
void isEqualToWithSameInstanceFromClassLevel() {
MockitoBeanOverrideHandler handler1 = createHandler(ClassLevelStringMockByName1.class);
assertThat(handler1).isEqualTo(handler1);
assertThat(handler1).hasSameHashCodeAs(handler1);
MockitoBeanOverrideHandler handler2 = createHandler(ClassLevelStringMockByType1.class);
assertThat(handler2).isEqualTo(handler2);
assertThat(handler2).hasSameHashCodeAs(handler2);
}
@Test // gh-33925
void isEqualToWithSameByNameLookupMetadataFromClassLevel() {
MockitoBeanOverrideHandler handler1 = createHandler(ClassLevelStringMockByName1.class);
MockitoBeanOverrideHandler handler2 = createHandler(ClassLevelStringMockByName2.class);
assertThat(handler1).isEqualTo(handler2);
assertThat(handler2).isEqualTo(handler1);
assertThat(handler1).hasSameHashCodeAs(handler2);
}
@Test // gh-33925
void isNotEqualToWithDifferentByNameLookupMetadataFromClassLevel() {
MockitoBeanOverrideHandler handler1 = createHandler(ClassLevelStringMockByName1.class);
MockitoBeanOverrideHandler handler2 = createHandler(ClassLevelStringMockByName3.class);
assertThat(handler1).isNotEqualTo(handler2);
assertThat(handler2).isNotEqualTo(handler1);
assertThat(handler1).doesNotHaveSameHashCodeAs(handler2);
}
@Test // gh-33925
void isEqualToWithSameByTypeLookupMetadataFromClassLevel() {
MockitoBeanOverrideHandler handler1 = createHandler(ClassLevelStringMockByType1.class);
MockitoBeanOverrideHandler handler2 = createHandler(ClassLevelStringMockByType2.class);
assertThat(handler1).isEqualTo(handler2);
assertThat(handler2).isEqualTo(handler1);
assertThat(handler1).hasSameHashCodeAs(handler2);
}
@Test // gh-33925
void isNotEqualToWithDifferentByTypeLookupMetadataFromClassLevel() {
MockitoBeanOverrideHandler handler1 = createHandler(ClassLevelStringMockByType1.class);
MockitoBeanOverrideHandler handler2 = createHandler(ClassLevelStringMockByType3.class);
assertThat(handler1).isNotEqualTo(handler2);
assertThat(handler2).isNotEqualTo(handler1);
assertThat(handler1).doesNotHaveSameHashCodeAs(handler2);
}
@Test // gh-33925
void isEqualToWithSameByNameLookupMetadataFromFieldAndClassLevel() {
MockitoBeanOverrideHandler handler1 = createHandler(sampleField("service3"));
MockitoBeanOverrideHandler handler2 = createHandler(ClassLevelStringMockByName1.class);
assertThat(handler1).isEqualTo(handler2);
assertThat(handler2).isEqualTo(handler1);
assertThat(handler1).hasSameHashCodeAs(handler2);
}
/**
* Since the "field name as fallback qualifier" is not available for an annotated class,
* what would seem to be "equivalent" handlers are actually not considered "equal" when
* the the lookup is "by type".
*/
@Test // gh-33925
void isNotEqualToWithSameByTypeLookupMetadataFromFieldAndClassLevel() {
MockitoBeanOverrideHandler handler1 = createHandler(sampleField("service"));
MockitoBeanOverrideHandler handler2 = createHandler(ClassLevelStringMockByType1.class);
assertThat(handler1).isNotEqualTo(handler2);
assertThat(handler2).isNotEqualTo(handler1);
assertThat(handler1).doesNotHaveSameHashCodeAs(handler2);
}
@Test
void isNotEqualEqualToByTypeLookupWithSameMetadataButDifferentField() {
MockitoBeanOverrideHandler handler1 = createHandler(sampleField("service"));
MockitoBeanOverrideHandler handler2 = createHandler(sampleField("service2"));
MockitoBeanOverrideHandler handler1 = createBeanOverrideHandler(sampleField("service"));
MockitoBeanOverrideHandler handler2 = createBeanOverrideHandler(sampleField("service2"));
assertThat(handler1).isNotEqualTo(handler2);
}
@Test
void isEqualEqualToByNameLookupWithSameMetadataButDifferentField() {
MockitoBeanOverrideHandler handler1 = createHandler(sampleField("service3"));
MockitoBeanOverrideHandler handler2 = createHandler(sampleField("service4"));
MockitoBeanOverrideHandler handler1 = createBeanOverrideHandler(sampleField("service3"));
MockitoBeanOverrideHandler handler2 = createBeanOverrideHandler(sampleField("service4"));
assertThat(handler1).isEqualTo(handler2);
assertThat(handler1).hasSameHashCodeAs(handler2);
}
@Test
void isNotEqualToWithSameMetadataButDifferentBeanName() {
MockitoBeanOverrideHandler handler1 = createHandler(sampleField("service"));
MockitoBeanOverrideHandler handler2 = createHandler(sampleField("service3"));
MockitoBeanOverrideHandler handler1 = createBeanOverrideHandler(sampleField("service"));
MockitoBeanOverrideHandler handler2 = createBeanOverrideHandler(sampleField("service3"));
assertThat(handler1).isNotEqualTo(handler2);
}
@Test
void isNotEqualToWithSameMetadataButDifferentExtraInterfaces() {
MockitoBeanOverrideHandler handler1 = createHandler(sampleField("service"));
MockitoBeanOverrideHandler handler2 = createHandler(sampleField("service5"));
MockitoBeanOverrideHandler handler1 = createBeanOverrideHandler(sampleField("service"));
MockitoBeanOverrideHandler handler2 = createBeanOverrideHandler(sampleField("service5"));
assertThat(handler1).isNotEqualTo(handler2);
}
@Test
void isNotEqualToWithSameMetadataButDifferentAnswers() {
MockitoBeanOverrideHandler handler1 = createHandler(sampleField("service"));
MockitoBeanOverrideHandler handler2 = createHandler(sampleField("service6"));
MockitoBeanOverrideHandler handler1 = createBeanOverrideHandler(sampleField("service"));
MockitoBeanOverrideHandler handler2 = createBeanOverrideHandler(sampleField("service6"));
assertThat(handler1).isNotEqualTo(handler2);
}
@Test
void isNotEqualToWithSameMetadataButDifferentSerializableFlag() {
MockitoBeanOverrideHandler handler1 = createHandler(sampleField("service"));
MockitoBeanOverrideHandler handler2 = createHandler(sampleField("service7"));
MockitoBeanOverrideHandler handler1 = createBeanOverrideHandler(sampleField("service"));
MockitoBeanOverrideHandler handler2 = createBeanOverrideHandler(sampleField("service7"));
assertThat(handler1).isNotEqualTo(handler2);
}
private static Field sampleField(String fieldName) {
private Field sampleField(String fieldName) {
Field field = ReflectionUtils.findField(Sample.class, fieldName);
assertThat(field).isNotNull();
return field;
}
private static MockitoBeanOverrideHandler createHandler(Field field) {
private MockitoBeanOverrideHandler createBeanOverrideHandler(Field field) {
MockitoBean annotation = AnnotatedElementUtils.getMergedAnnotation(field, MockitoBean.class);
return new MockitoBeanOverrideHandler(field, ResolvableType.forClass(field.getType()), annotation);
}
private MockitoBeanOverrideHandler createHandler(Class<?> clazz) {
MockitoBean annotation = AnnotatedElementUtils.getMergedAnnotation(clazz, MockitoBean.class);
return new MockitoBeanOverrideHandler(null, ResolvableType.forClass(annotation.types()[0]), annotation);
}
static class SampleOneMock {
@MockitoBean
String service;
}
static class SampleOneMockWithName {
@MockitoBean("anotherService")
String service;
}
static class Sample {
@@ -232,30 +156,7 @@ class MockitoBeanOverrideHandlerTests {
@MockitoBean(serializable = true)
private String service7;
}
@MockitoBean(name = "beanToMock", types = String.class)
static class ClassLevelStringMockByName1 {
}
@MockitoBean(name = "beanToMock", types = String.class)
static class ClassLevelStringMockByName2 {
}
@MockitoBean(name = "otherBeanToMock", types = String.class)
static class ClassLevelStringMockByName3 {
}
@MockitoBean(types = String.class)
static class ClassLevelStringMockByType1 {
}
@MockitoBean(types = String.class)
static class ClassLevelStringMockByType2 {
}
@MockitoBean(types = Integer.class)
static class ClassLevelStringMockByType3 {
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -18,180 +18,62 @@ package org.springframework.test.context.bean.override.mockito;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.List;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.Nullable;
import org.springframework.test.context.bean.override.BeanOverrideHandler;
import org.springframework.util.ReflectionUtils;
import org.springframework.test.context.bean.override.example.ExampleService;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Tests for {@link MockitoBeanOverrideProcessor}.
*
* @author Simon Baslé
* @author Sam Brannen
* @since 6.2
* Tests for {@link MockitoBeanOverrideProcessorTests}
*/
class MockitoBeanOverrideProcessorTests {
public class MockitoBeanOverrideProcessorTests {
private final MockitoBeanOverrideProcessor processor = new MockitoBeanOverrideProcessor();
@Test
void mockAnnotationCreatesMockitoBeanOverrideHandler() throws NoSuchFieldException {
MockitoBean annotation = AnnotationUtils.synthesizeAnnotation(MockitoBean.class);
Class<?> clazz = MockitoConf.class;
Field field = clazz.getField("a");
BeanOverrideHandler object = this.processor.createHandler(annotation, clazz, field);
@Nested
class CreateHandlerTests {
private final Field field = ReflectionUtils.findField(TestCase.class, "number");
@Test
void mockAnnotationCreatesMockitoBeanOverrideHandler() {
MockitoBean annotation = AnnotationUtils.synthesizeAnnotation(MockitoBean.class);
BeanOverrideHandler object = processor.createHandler(annotation, TestCase.class, field);
assertThat(object).isExactlyInstanceOf(MockitoBeanOverrideHandler.class);
}
@Test
void spyAnnotationCreatesMockitoSpyBeanOverrideHandler() {
MockitoSpyBean annotation = AnnotationUtils.synthesizeAnnotation(MockitoSpyBean.class);
BeanOverrideHandler object = processor.createHandler(annotation, TestCase.class, field);
assertThat(object).isExactlyInstanceOf(MockitoSpyBeanOverrideHandler.class);
}
@Test
void otherAnnotationThrows() {
Annotation annotation = field.getAnnotation(Nullable.class);
assertThatIllegalStateException()
.isThrownBy(() -> processor.createHandler(annotation, TestCase.class, field))
.withMessage("Invalid annotation passed to MockitoBeanOverrideProcessor: expected either " +
"@MockitoBean or @MockitoSpyBean on field %s.%s", field.getDeclaringClass().getName(),
field.getName());
}
@Test
void typesNotSupportedAtFieldLevel() {
Field field = ReflectionUtils.findField(TestCase.class, "typesNotSupported");
MockitoBean annotation = field.getAnnotation(MockitoBean.class);
assertThatIllegalStateException()
.isThrownBy(() -> processor.createHandler(annotation, TestCase.class, field))
.withMessage("The @MockitoBean 'types' attribute must be omitted when declared on a field");
}
static class TestCase {
@Nullable
@MockitoBean
@MockitoSpyBean
Integer number;
@MockitoBean(types = Integer.class)
String typesNotSupported;
}
@MockitoBean(name = "bogus", types = Integer.class)
static class NameNotSupportedTestCase {
}
assertThat(object).isExactlyInstanceOf(MockitoBeanOverrideHandler.class);
}
@Nested
class CreateHandlersTests {
@Test
void spyAnnotationCreatesMockitoSpyBeanOverrideHandler() throws NoSuchFieldException {
MockitoSpyBean annotation = AnnotationUtils.synthesizeAnnotation(MockitoSpyBean.class);
Class<?> clazz = MockitoConf.class;
Field field = clazz.getField("a");
BeanOverrideHandler object = this.processor.createHandler(annotation, clazz, field);
@Test
void missingTypes() {
Class<?> testClass = MissingTypesTestCase.class;
MockitoBean annotation = testClass.getAnnotation(MockitoBean.class);
assertThat(object).isExactlyInstanceOf(MockitoSpyBeanOverrideHandler.class);
}
assertThatIllegalStateException()
.isThrownBy(() -> processor.createHandlers(annotation, testClass))
.withMessage("The @MockitoBean 'types' attribute must not be empty when declared on a class");
}
@Test
void nameNotSupportedWithMultipleTypes() {
Class<?> testClass = NameNotSupportedWithMultipleTypesTestCase.class;
MockitoBean annotation = testClass.getAnnotation(MockitoBean.class);
assertThatIllegalStateException()
.isThrownBy(() -> processor.createHandlers(annotation, testClass))
.withMessage("The @MockitoBean 'name' attribute cannot be used when mocking multiple types");
}
@Test
void singleMockByType() {
Class<?> testClass = SingleMockByTypeTestCase.class;
MockitoBean annotation = testClass.getAnnotation(MockitoBean.class);
List<BeanOverrideHandler> handlers = processor.createHandlers(annotation, testClass);
assertThat(handlers).singleElement().isInstanceOfSatisfying(MockitoBeanOverrideHandler.class, handler -> {
assertThat(handler.getField()).isNull();
assertThat(handler.getBeanName()).isNull();
assertThat(handler.getBeanType().resolve()).isEqualTo(Integer.class);
});
}
@Test
void singleMockByName() {
Class<?> testClass = SingleMockByNameTestCase.class;
MockitoBean annotation = testClass.getAnnotation(MockitoBean.class);
List<BeanOverrideHandler> handlers = processor.createHandlers(annotation, testClass);
assertThat(handlers).singleElement().isInstanceOfSatisfying(MockitoBeanOverrideHandler.class, handler -> {
assertThat(handler.getField()).isNull();
assertThat(handler.getBeanName()).isEqualTo("enigma");
assertThat(handler.getBeanType().resolve()).isEqualTo(Integer.class);
});
}
@Test
void multipleMocks() {
Class<?> testClass = MultipleMocksTestCase.class;
MockitoBean annotation = testClass.getAnnotation(MockitoBean.class);
List<BeanOverrideHandler> handlers = processor.createHandlers(annotation, testClass);
assertThat(handlers).satisfiesExactly(
handler1 -> {
assertThat(handler1.getField()).isNull();
assertThat(handler1.getBeanName()).isNull();
assertThat(handler1.getBeanType().resolve()).isEqualTo(Integer.class);
},
handler2 -> {
assertThat(handler2.getField()).isNull();
assertThat(handler2.getBeanName()).isNull();
assertThat(handler2.getBeanType().resolve()).isEqualTo(Float.class);
}
);
}
@Test
void otherAnnotationThrows() throws NoSuchFieldException {
Class<?> clazz = MockitoConf.class;
Field field = clazz.getField("a");
Annotation annotation = field.getAnnotation(Nullable.class);
assertThatIllegalStateException()
.isThrownBy(() -> this.processor.createHandler(annotation, clazz, field))
.withMessage("Invalid annotation passed to MockitoBeanOverrideProcessor: expected either " +
"@MockitoBean or @MockitoSpyBean on field %s.%s", field.getDeclaringClass().getName(),
field.getName());
}
static class MockitoConf {
@Nullable
@MockitoBean
static class MissingTypesTestCase {
}
@MockitoBean(name = "bogus", types = { Integer.class, Float.class })
static class NameNotSupportedWithMultipleTypesTestCase {
}
@MockitoBean(types = Integer.class)
static class SingleMockByTypeTestCase {
}
@MockitoBean(name = "enigma", types = Integer.class)
static class SingleMockByNameTestCase {
}
@MockitoBean(types = { Integer.class, Float.class })
static class MultipleMocksTestCase {
}
@MockitoSpyBean
public ExampleService a;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -24,7 +24,6 @@ import org.junit.jupiter.api.Test;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.test.context.bean.override.BeanOverrideHandler;
import org.springframework.test.context.bean.override.BeanOverrideTestUtils;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -37,14 +36,14 @@ import static org.assertj.core.api.Assertions.assertThat;
class MockitoSpyBeanOverrideHandlerTests {
@Test
void beanNameIsSetToNullIfAnnotationNameIsEmpty() {
List<BeanOverrideHandler> list = BeanOverrideTestUtils.findHandlers(SampleOneSpy.class);
void forTestClassSetsNameToNullIfAnnotationNameIsNull() {
List<BeanOverrideHandler> list = BeanOverrideHandler.forTestClass(SampleOneSpy.class);
assertThat(list).singleElement().satisfies(handler -> assertThat(handler.getBeanName()).isNull());
}
@Test
void beanNameIsSetToAnnotationName() {
List<BeanOverrideHandler> list = BeanOverrideTestUtils.findHandlers(SampleOneSpyWithName.class);
void forTestClassSetsNameToAnnotationName() {
List<BeanOverrideHandler> list = BeanOverrideHandler.forTestClass(SampleOneSpyWithName.class);
assertThat(list).singleElement().satisfies(handler -> assertThat(handler.getBeanName()).isEqualTo("anotherService"));
}
@@ -1,102 +0,0 @@
/*
* Copyright 2002-2025 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.mockbeans;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
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.mockito.MockitoBean;
import org.springframework.test.context.bean.override.mockito.MockitoBeans;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
/**
* Integration tests for {@link MockitoBeans @MockitoBeans} and
* {@link MockitoBean @MockitoBean} declared "by name" at the class level as a
* repeatable annotation.
*
* @author Sam Brannen
* @since 6.2.2
* @see <a href="https://github.com/spring-projects/spring-framework/issues/33925">gh-33925</a>
* @see MockitoBeansByTypeIntegrationTests
*/
@SpringJUnitConfig
@MockitoBean(name = "s1", types = ExampleService.class)
@MockitoBean(name = "s2", types = ExampleService.class)
class MockitoBeansByNameIntegrationTests {
@Autowired
ExampleService s1;
@Autowired
ExampleService s2;
@MockitoBean(name = "s3")
ExampleService service3;
@Autowired
@Qualifier("s4")
ExampleService service4;
@BeforeEach
void configureMocks() {
given(s1.greeting()).willReturn("mock 1");
given(s2.greeting()).willReturn("mock 2");
given(service3.greeting()).willReturn("mock 3");
}
@Test
void checkMocksAndStandardBean() {
assertThat(s1.greeting()).isEqualTo("mock 1");
assertThat(s2.greeting()).isEqualTo("mock 2");
assertThat(service3.greeting()).isEqualTo("mock 3");
assertThat(service4.greeting()).isEqualTo("prod 4");
}
@Configuration
static class Config {
@Bean
ExampleService s1() {
return () -> "prod 1";
}
@Bean
ExampleService s2() {
return () -> "prod 2";
}
@Bean
ExampleService s3() {
return () -> "prod 3";
}
@Bean
ExampleService s4() {
return () -> "prod 4";
}
}
}
@@ -1,147 +0,0 @@
/*
* Copyright 2002-2025 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.mockbeans;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.context.bean.override.mockito.MockitoBeans;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
/**
* Integration tests for {@link MockitoBeans @MockitoBeans} and
* {@link MockitoBean @MockitoBean} declared "by type" at the class level, as a
* repeatable annotation, and via a custom composed annotation.
*
* @author Sam Brannen
* @since 6.2.2
* @see <a href="https://github.com/spring-projects/spring-framework/issues/33925">gh-33925</a>
* @see MockitoBeansByNameIntegrationTests
*/
@SpringJUnitConfig
@MockitoBean(types = {Service04.class, Service05.class})
@SharedMocks // Intentionally declared between local @MockitoBean declarations
@MockitoBean(types = Service06.class)
class MockitoBeansByTypeIntegrationTests implements TestInterface01 {
@Autowired
Service01 service01;
@Autowired
Service02 service02;
@Autowired
Service03 service03;
@Autowired
Service04 service04;
@Autowired
Service05 service05;
@Autowired
Service06 service06;
@MockitoBean
Service07 service07;
@BeforeEach
void configureMocks() {
given(service01.greeting()).willReturn("mock 01");
given(service02.greeting()).willReturn("mock 02");
given(service03.greeting()).willReturn("mock 03");
given(service04.greeting()).willReturn("mock 04");
given(service05.greeting()).willReturn("mock 05");
given(service06.greeting()).willReturn("mock 06");
given(service07.greeting()).willReturn("mock 07");
}
@Test
void checkMocks() {
assertThat(service01.greeting()).isEqualTo("mock 01");
assertThat(service02.greeting()).isEqualTo("mock 02");
assertThat(service03.greeting()).isEqualTo("mock 03");
assertThat(service04.greeting()).isEqualTo("mock 04");
assertThat(service05.greeting()).isEqualTo("mock 05");
assertThat(service06.greeting()).isEqualTo("mock 06");
assertThat(service07.greeting()).isEqualTo("mock 07");
}
@MockitoBean(types = Service09.class)
static class BaseTestCase implements TestInterface08 {
@Autowired
Service08 service08;
@Autowired
Service09 service09;
@MockitoBean
Service10 service10;
}
@Nested
@MockitoBean(types = Service12.class)
class NestedTests extends BaseTestCase implements TestInterface11 {
@Autowired
Service11 service11;
@Autowired
Service12 service12;
@MockitoBean
Service13 service13;
@BeforeEach
void configureMocks() {
given(service08.greeting()).willReturn("mock 08");
given(service09.greeting()).willReturn("mock 09");
given(service10.greeting()).willReturn("mock 10");
given(service11.greeting()).willReturn("mock 11");
given(service12.greeting()).willReturn("mock 12");
given(service13.greeting()).willReturn("mock 13");
}
@Test
void checkMocks() {
assertThat(service01.greeting()).isEqualTo("mock 01");
assertThat(service02.greeting()).isEqualTo("mock 02");
assertThat(service03.greeting()).isEqualTo("mock 03");
assertThat(service04.greeting()).isEqualTo("mock 04");
assertThat(service05.greeting()).isEqualTo("mock 05");
assertThat(service06.greeting()).isEqualTo("mock 06");
assertThat(service07.greeting()).isEqualTo("mock 07");
assertThat(service08.greeting()).isEqualTo("mock 08");
assertThat(service09.greeting()).isEqualTo("mock 09");
assertThat(service10.greeting()).isEqualTo("mock 10");
assertThat(service11.greeting()).isEqualTo("mock 11");
assertThat(service12.greeting()).isEqualTo("mock 12");
assertThat(service13.greeting()).isEqualTo("mock 13");
}
}
}
@@ -1,68 +0,0 @@
/*
* Copyright 2002-2025 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.mockbeans;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.springframework.core.ResolvableType;
import org.springframework.test.context.bean.override.BeanOverrideHandler;
import org.springframework.test.context.bean.override.BeanOverrideTestUtils;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.context.bean.override.mockito.MockitoBeans;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link MockitoBeans @MockitoBeans}: {@link MockitoBean @MockitoBean}
* declared at the class level, as a repeatable annotation, and via a custom composed
* annotation.
*
* @author Sam Brannen
* @since 6.2.2
* @see <a href="https://github.com/spring-projects/spring-framework/issues/33925">gh-33925</a>
*/
class MockitoBeansTests {
@Test
void registrationOrderForTopLevelClass() {
Stream<Class<?>> mockedServices = getRegisteredMockTypes(MockitoBeansByTypeIntegrationTests.class);
assertThat(mockedServices).containsExactly(
Service01.class, Service02.class, Service03.class, Service04.class,
Service05.class, Service06.class, Service07.class);
}
@Test
void registrationOrderForNestedClass() {
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,
Service13.class);
}
private static Stream<Class<?>> getRegisteredMockTypes(Class<?> testClass) {
return BeanOverrideTestUtils.findAllHandlers(testClass)
.stream()
.map(BeanOverrideHandler::getBeanType)
.map(ResolvableType::getRawClass);
}
}
@@ -1,21 +0,0 @@
/*
* Copyright 2002-2025 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.mockbeans;
interface Service {
String greeting();
}
@@ -1,20 +0,0 @@
/*
* Copyright 2002-2025 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.mockbeans;
interface Service01 extends Service {
}
@@ -1,20 +0,0 @@
/*
* Copyright 2002-2025 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.mockbeans;
interface Service02 extends Service {
}

Some files were not shown because too many files have changed in this diff Show More