Skip to content

Commit 1795b23

Browse files
committed
Merge branch '6.2.x'
2 parents 24c4401 + c168e1c commit 1795b23

File tree

52 files changed

+2970
-75
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+2970
-75
lines changed

Diff for: framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-mockitobean.adoc

+15
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,21 @@ the same bean in several test classes, make sure to name the fields consistently
4747
creating unnecessary contexts.
4848
====
4949

50+
[WARNING]
51+
====
52+
Using `@MockitoBean` or `@MockitoSpyBean` in conjunction with `@ContextHierarchy` can
53+
lead to undesirable results since each `@MockitoBean` or `@MockitoSpyBean` will be
54+
applied to all context hierarchy levels by default. To ensure that a particular
55+
`@MockitoBean` or `@MockitoSpyBean` is applied to a single context hierarchy level, set
56+
the `contextName` attribute to match a configured `@ContextConfiguration` name – for
57+
example, `@MockitoBean(contextName = "app-config")` or
58+
`@MockitoSpyBean(contextName = "app-config")`.
59+
60+
See
61+
xref:testing/testcontext-framework/ctx-management/hierarchies.adoc#testcontext-ctx-management-ctx-hierarchies-with-bean-overrides[context
62+
hierarchies with bean overrides] for further details and examples.
63+
====
64+
5065
Each annotation also defines Mockito-specific attributes to fine-tune the mocking behavior.
5166

5267
The `@MockitoBean` annotation uses the `REPLACE_OR_CREATE`

Diff for: framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-testbean.adoc

+13
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,19 @@ same bean in several tests, make sure to name the field consistently to avoid cr
3131
unnecessary contexts.
3232
====
3333

34+
[WARNING]
35+
====
36+
Using `@TestBean` in conjunction with `@ContextHierarchy` can lead to undesirable results
37+
since each `@TestBean` will be applied to all context hierarchy levels by default. To
38+
ensure that a particular `@TestBean` is applied to a single context hierarchy level, set
39+
the `contextName` attribute to match a configured `@ContextConfiguration` name – for
40+
example, `@TestBean(contextName = "app-config")`.
41+
42+
See
43+
xref:testing/testcontext-framework/ctx-management/hierarchies.adoc#testcontext-ctx-management-ctx-hierarchies-with-bean-overrides[context
44+
hierarchies with bean overrides] for further details and examples.
45+
====
46+
3447
[NOTE]
3548
====
3649
There are no restrictions on the visibility of `@TestBean` fields or factory methods.

Diff for: framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/hierarchies.adoc

+125-8
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,19 @@ given level in the hierarchy, the configuration resource type (that is, XML conf
2222
files or component classes) must be consistent. Otherwise, it is perfectly acceptable to
2323
have different levels in a context hierarchy configured using different resource types.
2424

25-
The remaining JUnit Jupiter based examples in this section show common configuration
26-
scenarios for integration tests that require the use of context hierarchies.
25+
[NOTE]
26+
====
27+
If you use `@DirtiesContext` in a test whose context is configured as part of a context
28+
hierarchy, you can use the `hierarchyMode` flag to control how the context cache is
29+
cleared.
30+
31+
For further details, see the discussion of `@DirtiesContext` in
32+
xref:testing/annotations/integration-spring/annotation-dirtiescontext.adoc[Spring Testing Annotations]
33+
and the {spring-framework-api}/test/annotation/DirtiesContext.html[`@DirtiesContext`] javadoc.
34+
====
35+
36+
The JUnit Jupiter based examples in this section show common configuration scenarios for
37+
integration tests that require the use of context hierarchies.
2738

2839
**Single test class with context hierarchy**
2940
--
@@ -229,12 +240,118 @@ Kotlin::
229240
class ExtendedTests : BaseTests() {}
230241
----
231242
======
243+
--
232244

233-
.Dirtying a context within a context hierarchy
234-
NOTE: If you use `@DirtiesContext` in a test whose context is configured as part of a
235-
context hierarchy, you can use the `hierarchyMode` flag to control how the context cache
236-
is cleared. For further details, see the discussion of `@DirtiesContext` in
237-
xref:testing/annotations/integration-spring/annotation-dirtiescontext.adoc[Spring Testing Annotations] and the
238-
{spring-framework-api}/test/annotation/DirtiesContext.html[`@DirtiesContext`] javadoc.
245+
[[testcontext-ctx-management-ctx-hierarchies-with-bean-overrides]]
246+
**Context hierarchies with bean overrides**
239247
--
248+
When `@ContextHierarchy` is used in conjunction with
249+
xref:testing/testcontext-framework/bean-overriding.adoc[bean overrides] such as
250+
`@TestBean`, `@MockitoBean`, or `@MockitoSpyBean`, it may be desirable or necessary to
251+
have the override applied to a single level in the context hierarchy. To achieve that,
252+
the bean override must specify a context name that matches a name configured via the
253+
`name` attribute in `@ContextConfiguration`.
254+
255+
The following test class configures the name of the second hierarchy level to be
256+
`"user-config"` and simultaneously specifies that the `UserService` should be wrapped in
257+
a Mockito spy in the context named `"user-config"`. Consequently, Spring will only
258+
attempt to create the spy in the `"user-config"` context and will not attempt to create
259+
the spy in the parent context.
260+
261+
[tabs]
262+
======
263+
Java::
264+
+
265+
[source,java,indent=0,subs="verbatim,quotes"]
266+
----
267+
@ExtendWith(SpringExtension.class)
268+
@ContextHierarchy({
269+
@ContextConfiguration(classes = AppConfig.class),
270+
@ContextConfiguration(classes = UserConfig.class, name = "user-config")
271+
})
272+
class IntegrationTests {
273+
274+
@MockitoSpyBean(contextName = "user-config")
275+
UserService userService;
276+
277+
// ...
278+
}
279+
----
280+
281+
Kotlin::
282+
+
283+
[source,kotlin,indent=0,subs="verbatim,quotes"]
284+
----
285+
@ExtendWith(SpringExtension::class)
286+
@ContextHierarchy(
287+
ContextConfiguration(classes = [AppConfig::class]),
288+
ContextConfiguration(classes = [UserConfig::class], name = "user-config"))
289+
class IntegrationTests {
290+
291+
@MockitoSpyBean(contextName = "user-config")
292+
lateinit var userService: UserService
293+
294+
// ...
295+
}
296+
----
297+
======
240298

299+
When applying bean overrides in different levels of the context hierarchy, you may need
300+
to have all of the bean override instances injected into the test class in order to
301+
interact with them — for example, to configure stubbing for mocks. However, `@Autowired`
302+
will always inject a matching bean found in the lowest level of the context hierarchy.
303+
Thus, to inject bean override instances from specific levels in the context hierarchy,
304+
you need to annotate fields with appropriate bean override annotations and configure the
305+
name of the context level.
306+
307+
The following test class configures the names of the hierarchy levels to be `"parent"`
308+
and `"child"`. It also declares two `PropertyService` fields that are configured to
309+
create or replace `PropertyService` beans with Mockito mocks in the respective contexts,
310+
named `"parent"` and `"child"`. Consequently, the mock from the `"parent"` context will
311+
be injected into the `propertyServiceInParent` field, and the mock from the `"child"`
312+
context will be injected into the `propertyServiceInChild` field.
313+
314+
[tabs]
315+
======
316+
Java::
317+
+
318+
[source,java,indent=0,subs="verbatim,quotes"]
319+
----
320+
@ExtendWith(SpringExtension.class)
321+
@ContextHierarchy({
322+
@ContextConfiguration(classes = ParentConfig.class, name = "parent"),
323+
@ContextConfiguration(classes = ChildConfig.class, name = "child")
324+
})
325+
class IntegrationTests {
326+
327+
@MockitoBean(contextName = "parent")
328+
PropertyService propertyServiceInParent;
329+
330+
@MockitoBean(contextName = "child")
331+
PropertyService propertyServiceInChild;
332+
333+
// ...
334+
}
335+
----
336+
337+
Kotlin::
338+
+
339+
[source,kotlin,indent=0,subs="verbatim,quotes"]
340+
----
341+
@ExtendWith(SpringExtension::class)
342+
@ContextHierarchy(
343+
ContextConfiguration(classes = [ParentConfig::class], name = "parent"),
344+
ContextConfiguration(classes = [ChildConfig::class], name = "child"))
345+
class IntegrationTests {
346+
347+
@MockitoBean(contextName = "parent")
348+
lateinit var propertyServiceInParent: PropertyService
349+
350+
@MockitoBean(contextName = "child")
351+
lateinit var propertyServiceInChild: PropertyService
352+
353+
// ...
354+
}
355+
----
356+
======
357+
--

Diff for: spring-test/src/main/java/org/springframework/test/context/ContextConfiguration.java

+12-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -292,13 +292,18 @@
292292
* <p>If not specified the name will be inferred based on the numerical level
293293
* within all declared contexts within the hierarchy.
294294
* <p>This attribute is only applicable when used within a test class hierarchy
295-
* or enclosing class hierarchy that is configured using
296-
* {@code @ContextHierarchy}, in which case the name can be used for
297-
* <em>merging</em> or <em>overriding</em> this configuration with configuration
298-
* of the same name in hierarchy levels defined in superclasses or enclosing
299-
* classes. See the Javadoc for {@link ContextHierarchy @ContextHierarchy} for
300-
* details.
295+
* or enclosing class hierarchy that is configured using {@code @ContextHierarchy},
296+
* in which case the name can be used for <em>merging</em> or <em>overriding</em>
297+
* this configuration with configuration of the same name in hierarchy levels
298+
* defined in superclasses or enclosing classes. As of Spring Framework 6.2.6,
299+
* the name can also be used to identify the configuration in which a
300+
* <em>Bean Override</em> should be applied &mdash; for example,
301+
* {@code @MockitoBean(contextName = "child")}. See the Javadoc for
302+
* {@link ContextHierarchy @ContextHierarchy} for details.
301303
* @since 3.2.2
304+
* @see org.springframework.test.context.bean.override.mockito.MockitoBean#contextName @MockitoBean(contextName = ...)
305+
* @see org.springframework.test.context.bean.override.mockito.MockitoSpyBean#contextName @MockitoSpyBean(contextName = ...)
306+
* @see org.springframework.test.context.bean.override.convention.TestBean#contextName @TestBean(contextName = ...)
302307
*/
303308
String name() default "";
304309

Diff for: spring-test/src/main/java/org/springframework/test/context/ContextHierarchy.java

+75-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,10 +29,12 @@
2929
* ApplicationContexts} for integration tests.
3030
*
3131
* <h3>Examples</h3>
32+
*
3233
* <p>The following JUnit-based examples demonstrate common configuration
3334
* scenarios for integration tests that require the use of context hierarchies.
3435
*
3536
* <h4>Single Test Class with Context Hierarchy</h4>
37+
*
3638
* <p>{@code ControllerIntegrationTests} represents a typical integration testing
3739
* scenario for a Spring MVC web application by declaring a context hierarchy
3840
* consisting of two levels, one for the <em>root</em> {@code WebApplicationContext}
@@ -57,6 +59,7 @@
5759
* }</pre>
5860
*
5961
* <h4>Class Hierarchy with Implicit Parent Context</h4>
62+
*
6063
* <p>The following test classes define a context hierarchy within a test class
6164
* hierarchy. {@code AbstractWebTests} declares the configuration for a root
6265
* {@code WebApplicationContext} in a Spring-powered web application. Note,
@@ -83,12 +86,13 @@
8386
* public class RestWebServiceTests extends AbstractWebTests {}</pre>
8487
*
8588
* <h4>Class Hierarchy with Merged Context Hierarchy Configuration</h4>
89+
*
8690
* <p>The following classes demonstrate the use of <em>named</em> hierarchy levels
8791
* in order to <em>merge</em> the configuration for specific levels in a context
88-
* hierarchy. {@code BaseTests} defines two levels in the hierarchy, {@code parent}
89-
* and {@code child}. {@code ExtendedTests} extends {@code BaseTests} and instructs
92+
* hierarchy. {@code BaseTests} defines two levels in the hierarchy, {@code "parent"}
93+
* and {@code "child"}. {@code ExtendedTests} extends {@code BaseTests} and instructs
9094
* the Spring TestContext Framework to merge the context configuration for the
91-
* {@code child} hierarchy level, simply by ensuring that the names declared via
95+
* {@code "child"} hierarchy level, simply by ensuring that the names declared via
9296
* {@link ContextConfiguration#name} are both {@code "child"}. The result is that
9397
* three application contexts will be loaded: one for {@code "/app-config.xml"},
9498
* one for {@code "/user-config.xml"}, and one for <code>{"/user-config.xml",
@@ -111,6 +115,7 @@
111115
* public class ExtendedTests extends BaseTests {}</pre>
112116
*
113117
* <h4>Class Hierarchy with Overridden Context Hierarchy Configuration</h4>
118+
*
114119
* <p>In contrast to the previous example, this example demonstrates how to
115120
* <em>override</em> the configuration for a given named level in a context hierarchy
116121
* by setting the {@link ContextConfiguration#inheritLocations} flag to {@code false}.
@@ -131,6 +136,72 @@
131136
* )
132137
* public class ExtendedTests extends BaseTests {}</pre>
133138
*
139+
* <h4>Context Hierarchies with Bean Overrides</h4>
140+
*
141+
* <p>When {@code @ContextHierarchy} is used in conjunction with bean overrides such as
142+
* {@link org.springframework.test.context.bean.override.convention.TestBean @TestBean},
143+
* {@link org.springframework.test.context.bean.override.mockito.MockitoBean @MockitoBean}, or
144+
* {@link org.springframework.test.context.bean.override.mockito.MockitoSpyBean @MockitoSpyBean},
145+
* it may be desirable or necessary to have the override applied to a single level
146+
* in the context hierarchy. To achieve that, the bean override must specify a
147+
* context name that matches a name configured via {@link ContextConfiguration#name}.
148+
*
149+
* <p>The following test class configures the name of the second hierarchy level to be
150+
* {@code "user-config"} and simultaneously specifies that the {@code UserService} should
151+
* be wrapped in a Mockito spy in the context named {@code "user-config"}. Consequently,
152+
* Spring will only attempt to create the spy in the {@code "user-config"} context and will
153+
* not attempt to create the spy in the parent context.
154+
*
155+
* <pre class="code">
156+
* &#064;ExtendWith(SpringExtension.class)
157+
* &#064;ContextHierarchy({
158+
* &#064;ContextConfiguration(classes = AppConfig.class),
159+
* &#064;ContextConfiguration(classes = UserConfig.class, name = "user-config")
160+
* })
161+
* class IntegrationTests {
162+
*
163+
* &#064;MockitoSpyBean(contextName = "user-config")
164+
* UserService userService;
165+
*
166+
* // ...
167+
* }</pre>
168+
*
169+
* <p>When applying bean overrides in different levels of the context hierarchy, you may
170+
* need to have all of the bean override instances injected into the test class in order
171+
* to interact with them &mdash; for example, to configure stubbing for mocks. However,
172+
* {@link org.springframework.beans.factory.annotation.Autowired @Autowired} will always
173+
* inject a matching bean found in the lowest level of the context hierarchy. Thus, to
174+
* inject bean override instances from specific levels in the context hierarchy, you need
175+
* to annotate fields with appropriate bean override annotations and configure the name
176+
* of the context level.
177+
*
178+
* <p>The following test class configures the names of the hierarchy levels to be
179+
* {@code "parent"} and {@code "child"}. It also declares two {@code PropertyService}
180+
* fields that are configured to create or replace {@code PropertyService} beans with
181+
* Mockito mocks in the respective contexts, named {@code "parent"} and {@code "child"}.
182+
* Consequently, the mock from the {@code "parent"} context will be injected into the
183+
* {@code propertyServiceInParent} field, and the mock from the {@code "child"} context
184+
* will be injected into the {@code propertyServiceInChild} field.
185+
*
186+
* <pre class="code">
187+
* &#064;ExtendWith(SpringExtension.class)
188+
* &#064;ContextHierarchy({
189+
* &#064;ContextConfiguration(classes = ParentConfig.class, name = "parent"),
190+
* &#064;ContextConfiguration(classes = ChildConfig.class, name = "child")
191+
* })
192+
* class IntegrationTests {
193+
*
194+
* &#064;MockitoBean(contextName = "parent")
195+
* PropertyService propertyServiceInParent;
196+
*
197+
* &#064;MockitoBean(contextName = "child")
198+
* PropertyService propertyServiceInChild;
199+
*
200+
* // ...
201+
* }</pre>
202+
*
203+
* <h4>Miscellaneous</h4>
204+
*
134205
* <p>This annotation may be used as a <em>meta-annotation</em> to create custom
135206
* <em>composed annotations</em>.
136207
*

Diff for: spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerFactory.java

+12-6
Original file line numberDiff line numberDiff line change
@@ -42,19 +42,25 @@ class BeanOverrideContextCustomizerFactory implements ContextCustomizerFactory {
4242
public @Nullable BeanOverrideContextCustomizer createContextCustomizer(Class<?> testClass,
4343
List<ContextConfigurationAttributes> configAttributes) {
4444

45+
// Base the context name on the "closest" @ContextConfiguration declaration
46+
// within the type and enclosing class hierarchies of the test class.
47+
String contextName = configAttributes.get(0).getName();
4548
Set<BeanOverrideHandler> handlers = new LinkedHashSet<>();
46-
findBeanOverrideHandlers(testClass, handlers);
49+
findBeanOverrideHandlers(testClass, contextName, handlers);
4750
if (handlers.isEmpty()) {
4851
return null;
4952
}
5053
return new BeanOverrideContextCustomizer(handlers);
5154
}
5255

53-
private void findBeanOverrideHandlers(Class<?> testClass, Set<BeanOverrideHandler> handlers) {
54-
BeanOverrideHandler.findAllHandlers(testClass).forEach(handler ->
55-
Assert.state(handlers.add(handler), () ->
56-
"Duplicate BeanOverrideHandler discovered in test class %s: %s"
57-
.formatted(testClass.getName(), handler)));
56+
private void findBeanOverrideHandlers(Class<?> testClass, @Nullable String contextName, Set<BeanOverrideHandler> handlers) {
57+
BeanOverrideHandler.findAllHandlers(testClass).stream()
58+
// If a handler does not specify a context name, it always gets applied.
59+
// Otherwise, the handler's context name must match the current context name.
60+
.filter(handler -> handler.getContextName().isEmpty() || handler.getContextName().equals(contextName))
61+
.forEach(handler -> Assert.state(handlers.add(handler),
62+
() -> "Duplicate BeanOverrideHandler discovered in test class %s: %s"
63+
.formatted(testClass.getName(), handler)));
5864
}
5965

6066
}

0 commit comments

Comments
 (0)