Spring Framework
  1. Spring Framework
  2. SPR-5243

Support loading WebApplicationContexts with the TestContext Framework

    Details

    • Type: New Feature New Feature
    • Status: Closed
    • Priority: Major Major
    • Resolution: Complete
    • Affects Version/s: 2.5 final, 3.0 GA, 3.1 GA
    • Fix Version/s: 3.2 RC1
    • Component/s: Test, Web
    • Labels:
      None
    • Last commented by a User:
      false

      Description

      Status Quo

      When the Spring TestContext Framework was introduced in Spring 2.5, it supported loading an ApplicationContext from either XML or Java Properties files. Spring 3.1 introduced support for loading an ApplicationContext from annotated classes (e.g., @Configuration classes).

      The underlying implementation for the existing support creates a GenericApplicationContext; however, a GenericApplicationContext is not suitable for testing a web application since a web application relies on an implementation of WebApplicationContext (WAC).

      In order to integration test Spring-powered web applications the Spring TestContext Framework needs to be able to load a WebApplicationContext, either from XML configuration files or from annotated classes. Furthermore, the ServletContext used by such a WAC needs to be configurable within tests, and common context hierarchies must be supported (e.g., root and dispatcher WACs in a parent-child relationship).

      Original Author's Description

      While writing some MVC integration tests, context errors were thrown when loading an XmlViewResolver and when attempting to recover command object property validation errors using the RequestContext. The reason is that each of these requires access to a WebApplicationContext, not a GenericApplicationContext which the TestContext framework makes available by default.


      Goals

      • Introduce an annotation that allows developers to configure a mock ServletContext from within integration tests.
      • Introduce SmartContextLoaders that can load WebApplicationContexts from either XML or annotated classes, using the configured mock ServletContext.
      • Provide a means for developers to access the mocks for the HttpServletRequest and HttpServletResponse objects and ensure that thread-local state in Spring MVC is kept in sync with these mock objects.
      • Ensure that metadata used to create the WebApplicationContext (e.g., ServletContext path) is used to define the unique application context cache key.

      Deliverables

      1. Implement a SmartContextLoader that loads a WebApplicationContext from XML resource locations defined via @ContextConfiguration
      2. Implement a SmartContextLoader that loads a WebApplicationContext from annotated classes defined via @ContextConfiguration
      3. Introduce a new class-level @WebAppConfiguration annotation that allows for configuration of the ServletContext base resource path, using Spring's Resource abstraction
        • see ContextMockMvcBuilder.configureWebAppRootDir() from spring-test-mvc
        • the base path must be filesystem-based by default, in contrast to the locations attribute in @ContextConfiguration which is classpath-based
        • the base path should default to "src/main/webapp", which follows the Maven convention
        • determine if @WebAppConfiguration should be inherited (i.e., annotated with @Inherited), keeping in mind that the top-level context in an EAR would not be a WAC
      4. Ensure that the two newly introduced SmartContextLoader implementations create a MockServletContext on demand (i.e., if a root WAC), when the WAC is loaded, and set the MockServletContext as the ServletContext in the application contexts that they load
      5. Set a loaded context as the ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE in the MockServletContext when context hierarchies are not used
      6. Introduce a subclass of MergedContextConfiguration specific for web apps (e.g., WebMergedContextConfiguration) that stores the ServletContext base path
        • the subclass of MCC must override equals() and hashCode() to include the metadata that uniquely identifies the resulting WAC for proper context caching
        • the buildMergedContextConfiguration() method in ContextLoaderUtils will likely need to instantiate either a standard MergedContextConfiguration or a WebMergedContextConfiguration
      7. Set up default thread local state via RequestContextHolder before each test method by implementing a new Servlet-specific TestExecutionListener
        • by using the MockServletContext already present in the WAC and by creating a MockHttpServletRequest, MockHttpServletResponse, and ServletWebRequest which will be set in the RequestContextHolder
      8. Ensure that the MockServletContext, MockHttpServletRequest, MockHttpServletResponse, and ServletWebRequest can be injected into the test instance (e.g., via @Autowired)
      9. Clean up thread locals after each test method
      10. Ensure that the Servlet-specific TestExecutionListener is configured as a default TestExecutionListener before DependencyInjectionTestExecutionListener
      11. Introduce a new web-specific DelegatingSmartContextLoader to incorporate support for the SmartContextLoader types introduced in this issue and ensure that the correct delegating loader is picked based on the presence or absence of @WebAppConfiguration
      12. Consider being able to accommodate a future request to support mocks for Spring Portlet MVC

      Pseudocode Examples


      Root WAC with Injected Mocks
      @WebAppConfiguration // path defaults to "file:src/main/webapp"
      @ContextConfiguration("file:src/main/webapp/WEB-INF/applicationContext.xml")
      public class RootWacTests {
      
          @Autowired
          private WebApplicationContext wac;
      
          @Autowired
          private MockServletContext servletContext;
      
          @Autowired
          private MockHttpServletRequest request;
      
          @Autowired
          private MockHttpServletResponse response;
      
          @Autowired
          private MockHttpSession session;
      
          @Autowired
          private ServletWebRequest webRequest;
      
          //...
      }
      

      Further Resources

      Blogs and Custom Solutions
      Forum Discussions

        Issue Links

          Activity

          Hide
          Rossen Stoyanchev added a comment -

          This helps writing tests for a POJO web controller that does not need application context infrastructure, but just access to a request/response pair and maybe a user session.

          The spring-test-mvc project (to be included in Spring 3.2) might fit this description. The "standalone" setup does not require an ApplicationContext and uses the mock request/response internally. See this example as well as many others in that package and sub-packages.

          Show
          Rossen Stoyanchev added a comment - This helps writing tests for a POJO web controller that does not need application context infrastructure, but just access to a request/response pair and maybe a user session. The spring-test-mvc project (to be included in Spring 3.2) might fit this description. The "standalone" setup does not require an ApplicationContext and uses the mock request/response internally. See this example as well as many others in that package and sub-packages.
          Hide
          Sam Brannen added a comment -

          If you're following this issue, you may interested in knowing that support for WebApplicationContext hierarchies in integration tests has been split out into a new issue: SPR-9863

          Regards,

          Sam

          Show
          Sam Brannen added a comment - If you're following this issue, you may interested in knowing that support for WebApplicationContext hierarchies in integration tests has been split out into a new issue: SPR-9863 Regards, Sam
          Hide
          Sam Brannen added a comment -
          Show
          Sam Brannen added a comment - Pull request submitted: https://github.com/SpringSource/spring-framework/pull/160
          Hide
          Sam Brannen added a comment -

          This work has been addressed as discussed in the comments for GitHub commit 9937f840d5:

          Support loading WebApplicationContexts in the TCF

          Prior to this commit, the Spring TestContext Framework only supported
          loading an ApplicationContext in integration tests from either XML or
          Java Properties files (since Spring 2.5), and Spring 3.1 introduced
          support for loading an ApplicationContext in integration tests from
          annotated classes (e.g., @Configuration classes). All of the
          ContextLoader implementations used to provide this support load a
          GenericApplicationContext. However, a GenericApplicationContext is not
          suitable for testing a web application since a web application relies on
          an implementation of WebApplicationContext (WAC).

          This commit makes it possible to integration test Spring-powered web
          applications by adding the following functionality to the Spring
          TestContext Framework.

          • Introduced AbstractGenericWebContextLoader and two concrete
            subclasses:
            • XmlWebContextLoader
            • AnnotationConfigWebContextLoader
          • Pulled up prepareContext(context, mergedConfig) from
            AbstractGenericContextLoader into AbstractContextLoader to allow it
            to be shared across web and non-web context loaders.
          • Introduced AnnotationConfigContextLoaderUtils and refactored
            AnnotationConfigContextLoader accordingly. These utils are also used
            by AnnotationConfigWebContextLoader.
          • Introduced a new @WebAppConfiguration annotation to denote that the
            ApplicationContext loaded for a test should be a WAC and to configure
            the base resource path for the root directory of a web application.
          • Introduced WebMergedContextConfiguration which extends
            MergedContextConfiguration with support for a baseResourcePath for
            the root directory of a web application.
          • ContextLoaderUtils.buildMergedContextConfiguration() now builds a
            WebMergedContextConfiguration instead of a standard
            MergedContextConfiguration if @WebAppConfiguration is present on the
            test class.
          • Introduced a configureWebResources() method in
            AbstractGenericWebContextLoader that is responsible for creating a
            MockServletContext with a proper ResourceLoader for the
            resourceBasePath configured in the WebMergedContextConfiguration. The
            resulting mock ServletContext is set in the WAC, and the WAC is
            stored as the Root WAC in the ServletContext.
          • Introduced a WebTestExecutionListener that sets up default thread
            local state via RequestContextHolder before each test method by using
            the MockServletContext already present in the WAC and by creating a
            MockHttpServletRequest, MockHttpServletResponse, and
            ServletWebRequest that is set in the RequestContextHolder. WTEL also
            ensures that the MockHttpServletResponse and ServletWebRequest can be
            injected into the test instance (e.g., via @Autowired) and cleans up
            thread locals after each test method.
          • WebTestExecutionListener is configured as a default
            TestExecutionListener before DependencyInjectionTestExecutionListener
          • Extracted AbstractDelegatingSmartContextLoader from
            DelegatingSmartContextLoader and introduced a new
            WebDelegatingSmartContextLoader.
          • ContextLoaderUtils now selects the default delegating ContextLoader
            class name based on the presence of @WebAppConfiguration on the test
            class.
          • Tests in the spring-test-mvc module no longer use a custom
            ContextLoader to load a WebApplicationContext. Instead, they now
            rely on new core functionality provided in this commit.
          Show
          Sam Brannen added a comment - This work has been addressed as discussed in the comments for GitHub commit 9937f840d5 : Support loading WebApplicationContexts in the TCF Prior to this commit, the Spring TestContext Framework only supported loading an ApplicationContext in integration tests from either XML or Java Properties files (since Spring 2.5), and Spring 3.1 introduced support for loading an ApplicationContext in integration tests from annotated classes (e.g., @Configuration classes). All of the ContextLoader implementations used to provide this support load a GenericApplicationContext. However, a GenericApplicationContext is not suitable for testing a web application since a web application relies on an implementation of WebApplicationContext (WAC). This commit makes it possible to integration test Spring-powered web applications by adding the following functionality to the Spring TestContext Framework. Introduced AbstractGenericWebContextLoader and two concrete subclasses: XmlWebContextLoader AnnotationConfigWebContextLoader Pulled up prepareContext(context, mergedConfig) from AbstractGenericContextLoader into AbstractContextLoader to allow it to be shared across web and non-web context loaders. Introduced AnnotationConfigContextLoaderUtils and refactored AnnotationConfigContextLoader accordingly. These utils are also used by AnnotationConfigWebContextLoader. Introduced a new @WebAppConfiguration annotation to denote that the ApplicationContext loaded for a test should be a WAC and to configure the base resource path for the root directory of a web application. Introduced WebMergedContextConfiguration which extends MergedContextConfiguration with support for a baseResourcePath for the root directory of a web application. ContextLoaderUtils.buildMergedContextConfiguration() now builds a WebMergedContextConfiguration instead of a standard MergedContextConfiguration if @WebAppConfiguration is present on the test class. Introduced a configureWebResources() method in AbstractGenericWebContextLoader that is responsible for creating a MockServletContext with a proper ResourceLoader for the resourceBasePath configured in the WebMergedContextConfiguration. The resulting mock ServletContext is set in the WAC, and the WAC is stored as the Root WAC in the ServletContext. Introduced a WebTestExecutionListener that sets up default thread local state via RequestContextHolder before each test method by using the MockServletContext already present in the WAC and by creating a MockHttpServletRequest, MockHttpServletResponse, and ServletWebRequest that is set in the RequestContextHolder. WTEL also ensures that the MockHttpServletResponse and ServletWebRequest can be injected into the test instance (e.g., via @Autowired) and cleans up thread locals after each test method. WebTestExecutionListener is configured as a default TestExecutionListener before DependencyInjectionTestExecutionListener Extracted AbstractDelegatingSmartContextLoader from DelegatingSmartContextLoader and introduced a new WebDelegatingSmartContextLoader. ContextLoaderUtils now selects the default delegating ContextLoader class name based on the presence of @WebAppConfiguration on the test class. Tests in the spring-test-mvc module no longer use a custom ContextLoader to load a WebApplicationContext. Instead, they now rely on new core functionality provided in this commit.
          Hide
          Sam Brannen added a comment -

          Note that WebTestExecutionListener has been renamed to ServletTestExecutionListener.

          Show
          Sam Brannen added a comment - Note that WebTestExecutionListener has been renamed to ServletTestExecutionListener .

            People

            • Assignee:
              Sam Brannen
              Reporter:
              Geoff Metselaar
              Last updater:
              Sam Brannen
            • Votes:
              30 Vote for this issue
              Watchers:
              29 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved:
                Days since last comment:
                1 year, 25 weeks ago

                Time Tracking

                Estimated:
                Original Estimate - Not Specified
                Not Specified
                Remaining:
                Remaining Estimate - 0d
                0d
                Logged:
                Time Spent - 4d
                4d