Skip to content

Provide support for context hierarchies in the TestContext Framework [SPR-5613] #10284

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
12 tasks done
spring-projects-issues opened this issue Mar 25, 2009 · 15 comments
Closed
12 tasks done
Assignees
Labels
has: votes-jira Issues migrated from JIRA with more than 10 votes at the time of import in: test Issues in the test module type: enhancement A general enhancement
Milestone

Comments

@spring-projects-issues
Copy link
Collaborator

spring-projects-issues commented Mar 25, 2009

Tomasz Wysocki opened SPR-5613 and commented

Status Quo

Currently the TestContext framework supports creating only flat, non-hierarchical contexts. There is no easy way to create contexts with parent-child relationships.


Goals

  • Add support for creating a test context with a parent context.
  • Configuration in the TestContext framework should allow for any number of levels in the context hierarchy.
  • Different levels in the context hierarchy may have different types of contexts (e.g., XML or annotation classes).
Example Hierarchies
  • Root WebApplicationContext <-- Dispatcher WebApplicationContext
  • EAR ApplicationContext <-- Root WebApplicationContext <-- Dispatcher WebApplicationContext

Deliverables

  1. Introduce a new @ContextHierarchy annotation that can contain nested @ContextConfiguration declarations
  2. Introduce a new name attribute in @ContextConfiguration that can be used for merging or overriding named @ContextConfiguration elements in the context hierarchy
  3. Assemble the context hierarchy for a given test class based on the levels defined in @ContextHierarchy in a single test class
  4. Assemble the context hierarchy for a given test class based on the levels defined in @ContextHierarchy and @ContextConfiguration within the test class hierarchy
  5. Support merging of configuration files or classes at any level in the hierarchy via the name attribute of @ContextConfiguration
  6. Support overriding of configuration files or classes at any level in the hierarchy via the name and inheritLocations attributes of @ContextConfiguration
  7. Cache each individual context within a hierarchy
  8. Support dirtying of the context hierarchy, including dirtying of any other affected hierarchies that share a common ancestor context
  9. Detect default XML resource location or default annotated classes, if feasible

Implementation Considerations

  • MergedContextConfiguration should have a reference to the parent MergedContextConfiguration (i.e., a private final field)
  • MergedContextConfiguration should provide a public ApplicationContext getParentApplicationContext() method that SmartContextLoader instances can use to retrieve and set the parent ApplicationContext
    • MergedContextConfiguration will need an internal reference to the context cache (or at least a way to retrieve the parent context from the cache)

Pseudocode Examples


Single Test Class with Context Hierarchy
@RunWith(SpringJUnit4ClassRunner.class)
@ContextHierarchy({
	@ContextConfiguration("parent.xml"),
	@ContextConfiguration("child.xml")
})
public class AppCtxHierarchyTests {}
Resulting Hierarchies
+------------+
| parent.xml |
+------------+
      ^
      |
+------------+
| child.xml  |
+------------+

Class Hierarchy with Implicit Parent Context
@RunWith(SpringJUnit4ClassRunner.class)
@ContextHierarchy(@ContextConfiguration(classes = AppConfig.class))
public abstract class AbstractTests {}

@ContextHierarchy(@ContextConfiguration("/A-config.xml"))
public class A extends AbstractTests {}

@ContextHierarchy(@ContextConfiguration("/B-config.xml"))
public class B extends AbstractTests {}
Resulting Hierarchies
+-----------------+
| AppConfig.class |
+-----------------+
         ^
         |
+-----------------+
|  A-config.xml   |
+-----------------+
+-----------------+
| AppConfig.class |
+-----------------+
         ^
         |
+-----------------+
|  B-config.xml   |
+-----------------+

Class Hierarchy with Bare @ContextConfiguration in a Superclass

In this scenario it is assumed that AbstractTests existed prior to the introduction of @ContextHierarchy support and that the author does not wish to (or cannot) modify AbstractTests to declare a @ContextHierarchy.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AppConfig.class)
public abstract class AbstractTests {}

@ContextHierarchy(@ContextConfiguration("/A-config.xml"))
public class A extends AbstractTests {}
Resulting Hierarchies
+-----------------+
| AppConfig.class |
+-----------------+
         ^
         |
+-----------------+
|  A-config.xml   |
+-----------------+

Class Hierarchy with Bare @ContextConfiguration in a Subclass

This scenario is not necessarily recommended, but it should still be supported for the sake of consistency.

Ideally, if the author of a test class knows that the context for the test class should take part in a context hierarchy, then the author should explicitly declare @ContextConfiguration within @ContextHierarchy.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextHierarchy(@ContextConfiguration(classes = AppConfig.class))
public abstract class AbstractTests {}

@ContextConfiguration("/A-config.xml")
public class A extends AbstractTests {}
Resulting Hierarchies
+-----------------+
| AppConfig.class |
+-----------------+
         ^
         |
+-----------------+
|  A-config.xml   |
+-----------------+

Class Hierarchy with Merged Configuration
@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 {}
Resulting Hierarchies
+----------------+
| app-config.xml |
+----------------+
        ^
        |
+-----------------+
| user-config.xml |
+-----------------+
          +----------------+
          | app-config.xml |
          +----------------+
                  ^
                  |
+-----------------------------------+
| user-config.xml, order-config.xml |
+-----------------------------------+

Class Hierarchy with Overridden Configuration
@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 {}
Resulting Hierarchies
+----------------+
| app-config.xml |
+----------------+
        ^
        |
+-----------------+
| user-config.xml |
+-----------------+
   +----------------+
   | app-config.xml |
   +----------------+
           ^
           |
+----------------------+
| test-user-config.xml |
+----------------------+

Affects: 2.5.6

Issue Links:

Referenced from: commits eefd1c4, 4c5d771, 98074e7

10 votes, 11 watchers

@spring-projects-issues
Copy link
Collaborator Author

Dave Syer commented

I have a suggestion: what about extending @ContextConfiguration to be an optional method-level annotation? The meaning would be: create a new context if there is none, or use the existing type-level value as a parent otherwise. I would also suggest the default should be to have an implicit @DirtiesContext applying only to the child context (with an option to switch it off I guess), otherwise the context cache could get quite large with no great benefit.

This would also be a great feature for integration testing where you want to override a handful of beans just for the test to provide a stubbed or alternative environment, or where you want to test multiple profiles in one go, without having to reset system properties.

@spring-projects-issues
Copy link
Collaborator Author

Tadaya Tsuyukubo commented

different implementation of the comment above, but I wrote a code to support parent app context for spring test framework.

pull request here:
#43

@spring-projects-issues
Copy link
Collaborator Author

spring-projects-issues commented Jun 28, 2012

Neale Upstone commented

Looking forward to seeing #9309 resolved by this, with the test context always as a child of the context specified in @ContextConfiguration :)

@spring-projects-issues
Copy link
Collaborator Author

Sam Brannen commented

If you are watching this issue, please feel free to participate in the discussion regarding the proposed Deliverables and the corresponding Pseudocode Examples listed in this issue's Description.

Thanks!

Sam

@spring-projects-issues
Copy link
Collaborator Author

Tadaya Tsuyukubo commented

Hi Sam,
I think it's a great idea to distinguish contexts by name.

I just want to poke around corner-case behaviors:

@ContextHierarchy( {
  @ContextConfiguration("parent.xml"), 
  @ContextConfiguration("child.xml")
})
public class BaseTests {}

@ContextHierarchy( {
  @ContextConfiguration(locations="another.xml", inheritLocations=true)
})
public class ExtendedTests extends BaseTests {}

In ExtendedTests, I didn't specify the name on the @ContextConfiguration, nor in BaseTests.

In this case, three contexts with this hierarchy to be expected?
"parent" <-- "child" <-- "another"

Another case:

@ContextHierarchy( {@ContextConfiguration("parent.xml")})
public class BaseTests {}

@ContextHierarchy( {@ContextConfiguration(locations="child.xml", inheritLocations=true)})
public class ExtendedTests extends BaseTests {}

Does this merge the configuration(parent.xml and child.xml)?
If so, behavior is slightly different from the first case.(inherit vs merge)

@spring-projects-issues
Copy link
Collaborator Author

Sam Brannen commented

Hi Tadaya,

Thanks for the feedback!

The answers to your questions are "yes" and "no", respectively.

Based on the current design, merging and overriding is only possible if the contexts in question are explicitly named; otherwise, unnamed contexts (or contexts with a different name) are considered to define separate levels in the hierarchy, following a top-down approach based on the location of the @ContextHierarchy annotation in the class hierarchy.

So, in your examples you would get:

  • parent <-- child <-- another
  • parent <-- child

Note that the inheritLocations attribute of @ContextConfiguration is true by default. So there is no need to declare it unless you are naming context hierarchy levels and overriding (i.e., setting inheritLocations to false).

Regards,

Sam

@spring-projects-issues
Copy link
Collaborator Author

Sam Brannen commented

Please note that I've added Resulting Hierarchies ASCII art to help everyone visualize the semantics of the proposal.

- Sam

@spring-projects-issues
Copy link
Collaborator Author

spring-projects-issues commented Jul 17, 2012

Tadaya Tsuyukubo commented

Thank you, Sam.

Another question:
Is @ContextHierarchy required annotation?
Or consider bare "@ContextConfiguration" is equal to "@ContextHierarchy({@ContextConfiguration})"
Or consider them different?

sample:

@ContextConfiguration("foo.xml")
public class Tests {}

is considered as this?

@ContextHierarchy( {@ContextConfiguration("foo.xml")} )
public class Tests {}

Based on the answer above, I think result of the following would be different:

@ContextConfiguration("parent.xml")
public class BaseTests {}

@ContextConfiguration("child.xml")
public class ExtendedTests extends BaseTests {}
  1. not valid (@ContextHierarchy is required)
  2. two contexts: "parent" <-- "child" (inheritance)
  3. or, merge "parent" and "child" => existing behavior

Regards,

@spring-projects-issues
Copy link
Collaborator Author

spring-projects-issues commented Jul 18, 2012

Sam Brannen commented

Tadaya,

Thanks again for your feedback. You are raising some very good questions! ;)

Is @ContextHierarchy required annotation?

In general, no, @ContextHierarchy is not a required annotation.

For backwards compatibility, the semantics for a stand-alone @ContextConfiguration annotation (in a class hierarchy that does not declare @ContextHierarchy) will remain consistent with the semantics defined since Spring 2.5.

However, @ContextHierarchy is required if you want a context hierarchy.

Or consider bare @ContextConfiguration is equal to @ContextHierarchy(@ContextConfiguration)

Yes, within a class hierarchy that declares context hierarchies, a bare @ContextConfiguration annotation will be interpreted as @ContextHierarchy(@ContextConfiguration).

For example, if @ContextHierarchy is present on a superclass A but subclass B only declares a bare @ContextConfiguration, then the context for the test class B will be a child of the context for test class A.

I'll add some more examples in the Pseudocode Examples section to make this clearer.


@ContextConfiguration("parent.xml")
public class BaseTests {}

@ContextConfiguration("child.xml")
public class ExtendedTests extends BaseTests {}

The above example has been a valid configuration since Spring 2.5. Thus the semantics for this kind of setup will remain unchanged. Specifically, no context hierarchy will be created. Instead, two contexts will be loaded, one for BaseTests using {"parent.xml"} and one for ExtendedTests using {"parent.xml", child.xml"}.

Regards,

Sam

@spring-projects-issues
Copy link
Collaborator Author

Sam Brannen commented

Hi everybody,

If you're actively following this issue, you may be interested in a sneak peak at the current code in my GitHub repository: https://github.com/sbrannen/spring-framework/tree/SPR-5613

The only part that is currently missing is support for dirtying a context (with @DirtiesContext) that resides in a context hierarchy.

So feel free to take it for a spin...

Feedback is welcome!

Sam

@spring-projects-issues
Copy link
Collaborator Author

Tadaya Tsuyukubo commented

Hi Sam,

I was looking your sneakpeak code and implementing the @DirtiesContext support for context hierarchy.

I was thinking the usage to be like this:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextHierarchy({
	@ContextConfiguration(name="foo", locations="/foo-config.xml"),
	@ContextConfiguration(name="bar", locations="/bar-config.xml"), 
	@ContextConfiguration(name="baz", locations="/baz-config.xml")
})
public class AppCtxHierarchyTests {
	@Test
	@DirtiesContext("bar")
	public void testTaintsBar(){...}
}

The above example would mark "bar" and it's child contexts such as "baz" as dirty.

If this is the expected behavior, I'll finish up the implementation and send a pull request.

Thanks,

@spring-projects-issues
Copy link
Collaborator Author

spring-projects-issues commented Feb 20, 2013

Tadaya Tsuyukubo commented

Hi Sam,
I wrote "@DirtiesContext context hierarchy support" on top of your #10284 branch based on the spec in my comment above.

pull request is here:
sbrannen#1

Thanks,

@spring-projects-issues
Copy link
Collaborator Author

Sam Brannen commented

Tadaya Tsuyukubo, thanks for submitting the pull request!

Unfortunately, using the names of context hierarchy levels to clear the context cache is fundamentally flawed. Please see my comments in your pull request for details.

Regards,

Sam

@spring-projects-issues
Copy link
Collaborator Author

Sam Brannen commented

Submitted pull request: #247

@spring-projects-issues
Copy link
Collaborator Author

Sam Brannen commented

Completed as described in the comments for GitHub commit 4c5d771764:

Provide support for context hierarchies in the TCF

Prior to this commit the Spring TestContext Framework supported creating
only flat, non-hierarchical contexts. There was no easy way to create
contexts with parent-child relationships.

This commit addresses this issue by introducing a new @ContextHierarchy
annotation that can be used in conjunction with @ContextConfiguration
for declaring hierarchies of application contexts, either within a
single test class or within a test class hierarchy. In addition,
@DirtiesContext now supports a new 'hierarchyMode' attribute for
controlling context cache clearing for context hierarchies.

  • Introduced a new @ContextHierarchy annotation.
  • Introduced 'name' attribute in @ContextConfiguration.
  • Introduced 'name' property in ContextConfigurationAttributes.
  • TestContext is now aware of @ContextHierarchy in addition to
    @ContextConfiguration.
  • Introduced findAnnotationDeclaringClassForTypes() in AnnotationUtils.
  • Introduced resolveContextHierarchyAttributes() in ContextLoaderUtils.
  • Introduced buildContextHierarchyMap() in ContextLoaderUtils.
  • @ContextConfiguration and @ContextHierarchy may not be used as
    top-level, class-level annotations simultaneously.
  • Introduced reference to the parent configuration in
    MergedContextConfiguration and WebMergedContextConfiguration.
  • Introduced overloaded buildMergedContextConfiguration() methods in
    ContextLoaderUtils in order to handle context hierarchies separately
    from conventional, non-hierarchical contexts.
  • Introduced hashCode() and equals() in ContextConfigurationAttributes.
  • ContextLoaderUtils ensures uniqueness of @ContextConfiguration
    elements within a single @ContextHierarchy declaration.
  • Introduced CacheAwareContextLoaderDelegate that can be used for
    loading contexts with transparent support for interacting with the
    context cache -- for example, for retrieving the parent application
    context in a context hierarchy.
  • TestContext now delegates to CacheAwareContextLoaderDelegate for
    loading contexts.
  • Introduced getParentApplicationContext() in MergedContextConfiguration
  • The loadContext(MergedContextConfiguration) methods in
    AbstractGenericContextLoader and AbstractGenericWebContextLoader now
    set the parent context as appropriate.
  • Introduced 'hierarchyMode' attribute in @DirtiesContext with a
    corresponding HierarchyMode enum that defines EXHAUSTIVE and
    CURRENT_LEVEL cache removal modes.
  • ContextCache now internally tracks the relationships between contexts
    that make up a context hierarchy. Furthermore, when a context is
    removed, if it is part of a context hierarchy all corresponding
    contexts will be removed from the cache according to the supplied
    HierarchyMode.
  • AbstractGenericWebContextLoader will set a loaded context as the
    ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE in the MockServletContext when
    context hierarchies are used if the context has no parent or if the
    context has a parent that is not a WAC.
  • Where appropriate, updated Javadoc to refer to the
    ServletTestExecutionListener, which was introduced in 3.2.0.
  • Updated Javadoc to avoid and/or suppress warnings in spring-test.
  • Suppressed remaining warnings in code in spring-test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
has: votes-jira Issues migrated from JIRA with more than 10 votes at the time of import in: test Issues in the test module type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

2 participants