Document context hierarchy support in the TCF

This commit updates the reference manual regarding the new support for
@ContextHierarchy and hierarchy modes in @DirtiesContext.

Issue: SPR-10357
This commit is contained in:
Sam Brannen
2013-03-11 02:26:33 +01:00
parent 88b9bea049
commit 4171646491
2 changed files with 327 additions and 56 deletions

View File

@@ -274,6 +274,12 @@
WebApplicationContext</link> in integration tests</para>
</listitem>
<listitem>
<para>Configuring <link
linkend="testcontext-ctx-management-ctx-hierarchies">context hierarchies</link>
in integration tests</para>
</listitem>
<listitem>
<para>Testing <link linkend="testcontext-web-scoped-beans">request and
session scoped beans</link></para>

View File

@@ -291,9 +291,9 @@
framework can be configured to reload the configuration and rebuild the
application context before executing the next test.</para>
<para>See context management and caching with the <link
linkend="testcontext-ctx-management">TestContext
framework</link>.</para>
<para>See <xref linkend="testcontext-ctx-management"/> and <xref
linkend="testcontext-ctx-management-caching"/> with the TestContext
framework.</para>
</section>
<section xml:id="testing-fixture-di">
@@ -511,10 +511,9 @@ public class CustomLoaderXmlApplicationContextTests {
declared by superclasses by default.</para>
</note>
<para>See <link linkend="testcontext-ctx-management">Context
management and caching</link> and the Javadoc for
<interfacename>@ContextConfiguration</interfacename> for further
details.</para>
<para>See <xref linkend="testcontext-ctx-management"/> and the
Javadoc for <interfacename>@ContextConfiguration</interfacename> for
further details.</para>
</listitem>
<listitem>
@@ -563,6 +562,50 @@ public class WebAppTests {
further details.</para>
</listitem>
<listitem>
<para><emphasis role="bold">
<interfacename>@ContextHierarchy</interfacename></emphasis></para>
<para>A class-level annotation that is used to define a hierarchy of
<interfacename>ApplicationContext</interfacename>s for integration
tests. <interfacename>@ContextHierarchy</interfacename> should be
declared with a list of one or more
<interfacename>@ContextConfiguration</interfacename> instances, each
of which defines a level in the context hierarchy. The following
examples demonstrate the use of
<interfacename>@ContextHierarchy</interfacename> within a single
test class; however,
<interfacename>@ContextHierarchy</interfacename> can also be used
within a test class hierarchy.</para>
<programlisting language="java">@ContextHierarchy({
@ContextConfiguration("/parent-config.xml"),
@ContextConfiguration("/child-config.xml")
})
public class ContextHierarchyTests {
<lineannotation>// class body...</lineannotation>
}</programlisting>
<programlisting language="java">@WebAppConfiguration
@ContextHierarchy({
@ContextConfiguration(classes = AppConfig.class),
@ContextConfiguration(classes = WebConfig.class)
})
public class WebIntegrationTests {
<lineannotation>// class body...</lineannotation>
}</programlisting>
<para>If you need to merge or override the configuration for a given
level of the context hierarchy within a test class hierarchy, you
must explicitly name that level by supplying the same value to the
<varname>name</varname> attribute in
<interfacename>@ContextConfiguration</interfacename> at each
corresponding level in the class hierarchy. See <xref
linkend="testcontext-ctx-management-ctx-hierarchies"/> and the
Javadoc for <interfacename>@ContextHierarchy</interfacename> for
further examples.</para>
</listitem>
<listitem>
<para><emphasis role="bold">
<interfacename>@ActiveProfiles</interfacename> </emphasis></para>
@@ -590,11 +633,9 @@ public class DeveloperIntegrationTests {
profiles declared by superclasses by default.</para>
</note>
<para>See <link
linkend="testcontext-ctx-management-env-profiles">Context
configuration with environment profiles</link> and the Javadoc for
<interfacename>@ActiveProfiles</interfacename> for examples and
further details.</para>
<para>See <xref linkend="testcontext-ctx-management-env-profiles"/>
and the Javadoc for <interfacename>@ActiveProfiles</interfacename>
for examples and further details.</para>
</listitem>
<listitem>
@@ -603,66 +644,98 @@ public class DeveloperIntegrationTests {
<para>Indicates that the underlying Spring
<interfacename>ApplicationContext</interfacename> has been
<emphasis>dirtied</emphasis> (i.e., modified or corrupted in some
manner) during the execution of a test and should be closed,
regardless of whether the test passed.
<interfacename>@DirtiesContext</interfacename> is supported in the
following scenarios:</para>
<emphasis>dirtied</emphasis> during the execution of a test (i.e.,
modified or corrupted in some manner — for example, by changing the
state of a singleton bean) and should be closed, regardless of
whether the test passed. When an application context is marked
<emphasis>dirty</emphasis>, it is removed from the testing
framework's cache and closed. As a consequence, the underlying
Spring container will be rebuilt for any subsequent test that
requires a context with the same configuration metadata.</para>
<para><interfacename>@DirtiesContext</interfacename> can be used as
both a class-level and method-level annotation within the same test
class. In such scenarios, the
<interfacename>ApplicationContext</interfacename> is marked as
<emphasis>dirty</emphasis> after any such annotated method as well
as after the entire class. If the <classname>ClassMode</classname>
is set to <literal>AFTER_EACH_TEST_METHOD</literal>, the context is
marked dirty after each test method in the class.</para>
<para>The following examples explain when the context would be
dirtied for various configuration scenarios:</para>
<itemizedlist>
<listitem>
<para>After the current test class, when declared on a class
with class mode set to <literal>AFTER_CLASS</literal>, which is
the default class mode.</para>
with class mode set to <literal>AFTER_CLASS</literal> (i.e., the
default class mode).</para>
<programlisting language="java"><emphasis role="bold">@DirtiesContext</emphasis>
public class ContextDirtyingTests {
<lineannotation>// some tests that result in the Spring container being dirtied</lineannotation>
}</programlisting>
</listitem>
<listitem>
<para>After each test method in the current test class, when
declared on a class with class mode set to
<literal>AFTER_EACH_TEST_METHOD.</literal></para>
<literal>AFTER_EACH_TEST_METHOD.</literal><programlisting
language="java"><emphasis role="bold">@DirtiesContext</emphasis>(<emphasis
role="bold">classMode</emphasis> = ClassMode.AFTER_EACH_TEST_METHOD)
public class ContextDirtyingTests {
<lineannotation>// some tests that result in the Spring container being dirtied</lineannotation>
}</programlisting></para>
</listitem>
<listitem>
<para>After the current test, when declared on a method.</para>
</listitem>
</itemizedlist>
<para>Use this annotation if a test has modified the context (for
example, by replacing a bean definition). Subsequent tests are
supplied a new context.</para>
<para>With JUnit 4.5+ or TestNG you can use
<interfacename>@DirtiesContext</interfacename> as both a class-level
and method-level annotation within the same test class. In such
scenarios, the <interfacename>ApplicationContext</interfacename> is
marked as <emphasis>dirty</emphasis> after any such annotated method
as well as after the entire class. If the
<classname>ClassMode</classname> is set to
<literal>AFTER_EACH_TEST_METHOD</literal>, the context is marked
dirty after each test method in the class.</para>
<programlisting language="java"><emphasis role="bold">@DirtiesContext</emphasis>
public class ContextDirtyingTests {
<lineannotation>// some tests that result in the Spring container being dirtied</lineannotation>
}</programlisting>
<programlisting language="java"><emphasis role="bold">@DirtiesContext</emphasis>(<emphasis
role="bold">classMode</emphasis> = ClassMode.AFTER_EACH_TEST_METHOD)
public class ContextDirtyingTests {
<lineannotation>// some tests that result in the Spring container being dirtied</lineannotation>
}</programlisting>
<programlisting language="java"><emphasis role="bold">@DirtiesContext</emphasis>
<programlisting language="java"><emphasis role="bold">@DirtiesContext</emphasis>
@Test
public void testProcessWhichDirtiesAppCtx() {
<lineannotation>// some logic that results in the Spring container being dirtied</lineannotation>
}</programlisting>
</listitem>
</itemizedlist>
<para>When an application context is marked
<emphasis>dirty</emphasis>, it is removed from the testing
framework's cache and closed; thus the underlying Spring container
is rebuilt for any subsequent test that requires a context with the
same configuration metadata.</para>
<para>If <interfacename>@DirtiesContext</interfacename> is used in a
test whose context is configured as part of a context hierarchy via
<interfacename>@ContextHierarchy</interfacename>, the
<varname>hierarchyMode</varname> flag can be used to control how the
context cache is cleared. By default an
<emphasis>exhaustive</emphasis> algorithm will be used that clears
the context cache including not only the current level but also all
other context hierarchies that share an ancestor context common to
the current test; all
<interfacename>ApplicationContext</interfacename>s that reside in a
sub-hierarchy of the common ancestor context will be removed from
the context cache and closed. If the <emphasis>exhaustive</emphasis>
algorithm is overkill for a particular use case, the simpler
<emphasis>current level</emphasis> algorithm can be specified
instead, as seen below.</para>
<programlisting language="java">@ContextHierarchy({
@ContextConfiguration("/parent-config.xml"),
@ContextConfiguration("/child-config.xml")
})
public class BaseTests {
<lineannotation>// class body...</lineannotation>
}
public class ExtendedTests extends BaseTests {
@Test
@DirtiesContext(<emphasis role="bold">hierarchyMode = HierarchyMode.CURRENT_LEVEL</emphasis>)
public void test() {
<lineannotation>// some logic that results in the child context being dirtied</lineannotation>
}
}</programlisting>
<para>For further details regarding the
<constant>EXHAUSTIVE</constant> and
<constant>CURRENT_LEVEL</constant> algorithms see the Javadoc for
<interfacename>DirtiesContext.HierarchyMode</interfacename>.</para>
</listitem>
<listitem>
@@ -1918,7 +1991,7 @@ public class WacTests {
<interfacename>@Configuration</interfacename> classes).</para>
<example>
<title>Default Resource Semantics</title>
<title>Default resource semantics</title>
<programlisting language="java">@RunWith(SpringJUnit4ClassRunner.class)
@@ -1945,7 +2018,7 @@ public class WacTests {
locations are classpath based.</para>
<example>
<title>Explicit Resource Semantics</title>
<title>Explicit resource semantics</title>
<programlisting language="java">@RunWith(SpringJUnit4ClassRunner.class)
@@ -2002,7 +2075,7 @@ public class WacTests {
<interfacename>ServletTestExecutionListener</interfacename>.</para>
<example>
<title>Injecting Mocks</title>
<title>Injecting mocks</title>
<programlisting language="java">@WebAppConfiguration
@ContextConfiguration
@@ -2135,6 +2208,198 @@ public class WacTests {
<classname>DirtiesContextTestExecutionListener</classname> which is
enabled by default.</para>
</section>
<section xml:id="testcontext-ctx-management-ctx-hierarchies">
<title>Context hierarchies</title>
<para>When writing integration tests that rely on a loaded Spring
<interfacename>ApplicationContext</interfacename>, it is often
sufficient to test against a single context; however, there are times
when it is beneficial or even necessary to test against a hierarchy of
<interfacename>ApplicationContext</interfacename>s. For example, if
you are developing a Spring MVC web application you will typically
have a root <interfacename>WebApplicationContext</interfacename>
loaded via Spring's <classname>ContextLoaderListener</classname> and a
child <interfacename>WebApplicationContext</interfacename> loaded via
Spring's <classname>DispatcherServlet</classname>. This results in a
parent-child context hierarchy where shared components and
infrastructure configuration are declared in the root context and
consumed in the child context by web-specific components. Another use
case can be found in Spring Batch applications where you often have a
parent context that provides configuration for shared batch
infrastructure and a child context for the configuration of a specific
batch job.</para>
<para>As of Spring Framework 3.2.2, it is possible to write
integration tests that use context hierarchies by declaring context
configuration via the <interfacename>@ContextHierarchy</interfacename>
annotation, either on an individual test class or within a test class
hierarchy. If a context hierarchy is declared on multiple classes
within a test class hierarchy it is also possible to merge or override
the context configuration for a specific, named level in the context
hierarchy. When merging configuration for a given level in the
hierarchy the configuration resource type (i.e., XML configuration
files or annotated classes) must be consistent; otherwise, it is
perfectly acceptable to have different levels in a context hierarchy
configured using different resource types.</para>
<para>The following JUnit-based examples demonstrate common
configuration scenarios for integration tests that require the use of
context hierarchies.</para>
<example>
<title>Single test class with context hierarchy</title>
<para><classname>ControllerIntegrationTests</classname> represents a
typical integration testing scenario for a Spring MVC web
application by declaring a context hierarchy consisting of two
levels, one for the <emphasis>root</emphasis> WebApplicationContext
(loaded using the <classname>TestAppConfig</classname>
<interfacename>@Configuration</interfacename> class) and one for the
<emphasis>dispatcher servlet</emphasis>
<interfacename>WebApplicationContext</interfacename> (loaded using
the <classname>WebConfig</classname>
<interfacename>@Configuration</interfacename> class). The
<interfacename>WebApplicationContext</interfacename> that is
<emphasis>autowired</emphasis> into the test instance is the one for
the child context (i.e., the lowest context in the
hierarchy).</para>
<programlisting language="java">@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextHierarchy({
@ContextConfiguration(classes = TestAppConfig.class),
@ContextConfiguration(classes = WebConfig.class)
})
public class ControllerIntegrationTests {
@Autowired
private WebApplicationContext wac;
// ...
}</programlisting>
</example>
<example>
<title>Class hierarchy with implicit parent context</title>
<para>The following test classes define a context hierarchy within a
test class hierarchy. <classname>AbstractWebTests</classname>
declares the configuration for a root
<interfacename>WebApplicationContext</interfacename> in a
Spring-powered web application. Note, however, that
<classname>AbstractWebTests</classname> does not declare
<interfacename>@ContextHierarchy</interfacename>; consequently,
subclasses of <classname>AbstractWebTests</classname> can optionally
participate in a context hierarchy or simply follow the standard
semantics for <interfacename>@ContextConfiguration</interfacename>.
<classname>SoapWebServiceTests</classname> and
<classname>RestWebServiceTests</classname> both extend
<classname>AbstractWebTests</classname> and define a context
hierarchy via <interfacename>@ContextHierarchy</interfacename>. The
result is that three application contexts will be loaded (one for
each declaration of
<interfacename>@ContextConfiguration</interfacename>), and the
application context loaded based on the configuration in
<classname>AbstractWebTests</classname> will be set as the parent
context for each of the contexts loaded for the concrete
subclasses.</para>
<programlisting language="java">@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration("file:src/main/webapp/WEB-INF/applicationContext.xml")
public abstract class AbstractWebTests {}
@ContextHierarchy(@ContextConfiguration("/spring/soap-ws-config.xml")
public class SoapWebServiceTests extends AbstractWebTests {}
@ContextHierarchy(@ContextConfiguration("/spring/rest-ws-config.xml")
public class RestWebServiceTests extends AbstractWebTests {}</programlisting>
</example>
<example>
<title>Class hierarchy with merged context hierarchy
configuration</title>
<para>The following classes demonstrate the use of
<emphasis>named</emphasis> hierarchy levels in order to
<emphasis>merge</emphasis> the configuration for specific levels in
a context hierarchy. <classname>BaseTests</classname> defines two
levels in the hierarchy, <literal>parent</literal> and
<literal>child</literal>. <classname>ExtendedTests</classname>
extends <classname>BaseTests</classname> and instructs the Spring
TestContext Framework to merge the context configuration for the
<literal>child</literal> hierarchy level, simply by ensuring that
the names declared via
<interfacename>ContextConfiguration</interfacename>'s
<varname>name</varname> attribute are both
<literal>"child"</literal>. The result is that three application
contexts will be loaded: one for
<literal>"/app-config.xml"</literal>, one for
<literal>"/user-config.xml"</literal>, and one for
<literal>{"/user-config.xml", "/order-config.xml"}</literal>. As
with the previous example, the application context loaded from
<literal>"/app-config.xml"</literal> will be set as the parent
context for the contexts loaded from
<literal>"/user-config.xml"</literal> and
<literal>{"/user-config.xml", "/order-config.xml"}</literal>.</para>
<programlisting language="java">@RunWith(SpringJUnit4ClassRunner.class)
@ContextHierarchy({
@ContextConfiguration(name = "parent", locations = "/app-config.xml"),
@ContextConfiguration(name = "child", locations = "/user-config.xml")
})
public class BaseTests {}
@ContextHierarchy(
@ContextConfiguration(name = "child", locations = "/order-config.xml")
)
public class ExtendedTests extends BaseTests {}</programlisting>
</example>
<example>
<title>Class hierarchy with overridden context hierarchy
configuration</title>
<para>In contrast to the previous example, this example demonstrates
how to <emphasis>override</emphasis> the configuration for a given
named level in a context hierarchy by setting
<interfacename>ContextConfiguration</interfacename>'s
<varname>inheritLocations</varname> flag to
<literal>false</literal>. Consequently, the application context for
<classname>ExtendedTests</classname> will be loaded only from
<literal>"/test-user-config.xml"</literal> and will have its parent
set to the context loaded from
<literal>"/app-config.xml"</literal>.</para>
<programlisting language="java">@RunWith(SpringJUnit4ClassRunner.class)
@ContextHierarchy({
@ContextConfiguration(name = "parent", locations = "/app-config.xml"),
@ContextConfiguration(name = "child", locations = "/user-config.xml")
})
public class BaseTests {}
@ContextHierarchy(
@ContextConfiguration(
name = "child",
locations = "/test-user-config.xml",
inheritLocations = false
))
public class ExtendedTests extends BaseTests {}</programlisting>
</example>
<note>
<title>Dirtying a context within a context hierarchy</title>
<para>If <interfacename>@DirtiesContext</interfacename> is used in a
test whose context is configured as part of a context hierarchy, the
<varname>hierarchyMode</varname> flag can be used to control how the
context cache is cleared. For further details consult the discussion
of <interfacename>@DirtiesContext</interfacename> in <xref
linkend="integration-testing-annotations-spring"/> and the Javadoc
for <interfacename>@DirtiesContext</interfacename>.</para>
</note>
</section>
</section>
<section xml:id="testcontext-fixture-di">