Skip to content
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

Support loading WebApplicationContexts with the TestContext Framework [SPR-5243] #9917

Closed
12 tasks done
spring-projects-issues opened this issue Oct 27, 2008 · 14 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 in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement
Milestone

Comments

@spring-projects-issues
Copy link
Collaborator

spring-projects-issues commented Oct 27, 2008

Geoff Metselaar opened SPR-5243 and commented

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

Affects: 2.5 final, 3.0 GA, 3.1 GA

Attachments:

Issue Links:

Referenced from: commits 461d99a, a281bdb, 90c5f22, 9937f84, a73280c, 21ebbb9

30 votes, 29 watchers

@spring-projects-issues
Copy link
Collaborator Author

Narada Sage commented

I would absolutely love this. I've spent a lot of time trying to get this to work. It shouldn't be this difficult. It'd be great if this not uncommon use case was available out of the box. Thanks.

@spring-projects-issues
Copy link
Collaborator Author

Gaetan Pitteloud commented

Loading a WebApplicationContext is not a difficult task on its own : you just provide a @ContextConfiguration.loader that creates a GenericWebApplicationContext. The difficult task is that the basic contract of a WebApplicationContext is that it knows the ServletContext for several reasons.

I have built a solution based on a test listener (so-called WebListener) that creates the web environment (based on spring mock classes) : Mock ServletContext, Mock request, response, session. The ServletContext is created once (beforeTestClass) for a test class, and is <i>injected</i> into the ContextLoader (there's no API to do this, thus I had to do it with reflexion, which is the goal of my TestContextUtils class). I have to ensure that the context is not loaded yet, or this trick will not work (I mark it as dirty for that purpose, and also because it might have been loaded by another test class earlier, or will be used by another test class later on).

You will find attached the WebListener and Web ContextLoader that are working together.

The web listener's duty also includes a way to inject the web mocks (ServletContext, request, response, etc.) into test instance variables that are annotated with @WebResource. I found it quite useful.

The WebListener is actually split into an AbstractWebListener and its subclass WebListener because I also implemented a JsfListener with the same semantics, with the only jsf mock lib I found so far : shale-test.

There are tests in the listener that test for the existence of an application context (by looking for a @ContextConfiguration annotation in the test class hiererchy) : the reason for this is that the web listener may also be used without an application context in place, i.e. only for creating a mock web environment.

The mock objects (request, response, session) are shared for other listeners to use thanks to TestContext internal attributes map, which also happens to be useful.

@spring-projects-issues
Copy link
Collaborator Author

Stevo Slavić commented

Checkout Spring MVC integration test support Rosen and Arjen have been building @ https://github.com/SpringSource/spring-test-mvc
First heard about it on S2G Forum 2011 Amsterdam, and Spring 3.2 was mentioned as likely target. Not sure if there is separate ticket for this, but I think they are related.

@spring-projects-issues
Copy link
Collaborator Author

Rossen Stoyanchev commented

The two might be quite complementary. You could use this to create and initialize a WebApplicationContext once per test fixture and then provide it to the main class of the Spring MVC integration test support.

@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

Gaetan Pitteloud commented

2 comments:

  • When @WebAppConfiguration is present, the WAC should not be cached and reused in next tests that share the same @ContextConfiguration.locations, because the lifespan of its ServletContext is limited to the test class.
  • I found it quite useful to have tests that run with mock environment (mock servlet context, request, response, session), RequestContext thread locals, etc., but that do not need a WAC to be set up. Thus we have lighter tests, still using mock injection and thread locals facilities. It would be nice to allow such tests.

And 2 questions:

  • Is there a plan to include some sort of security support, so that request.isUserInRole(String role) and request.getUserPrincipal() return some configurable values ? The idea would be a @Secured annotation on test methods (with username and roles), that defines the security context of a test case.
  • Does all this work with a test execution listener or will it be integrated into the TestContext framework ?

@spring-projects-issues
Copy link
Collaborator Author

Sam Brannen commented

Hi Gaetan,

When @WebAppConfiguration is present, the WAC should not be cached and reused in next tests that share the same @ContextConfiguration.locations, because the lifespan of its ServletContext is limited to the test class.

One of the key features of the TestContext framework is caching. Thus the WAC will certainly be cached by default.

Please note that the proposed subclass of MergedContextConfiguration will be responsible for ensuring that the MCC uniquely identifies the WAC for proper caching.

Why do you say that "the lifespan of its ServletContext is limited to the test class"?

I would argue that the lifespan of the ServletContext is tied directly to the lifespan of the corresponding WebApplicationContext. Thus if the WAC is used across multiple test classes, it should definitely be cached.

If you want to disable caching of a WAC after a particular test class, simply annotate the test class with @DirtiesContext.

I found it quite useful to have tests that run with mock environment (mock servlet context, request, response, session), RequestContext thread locals, etc., but that do not need a WAC to be set up. Thus we have lighter tests, still using mock injection and thread locals facilities. It would be nice to allow such tests.

How do you propose to inject the mocks if not from a configured WAC?

Are you proposing to just have a custom TestExecutionListener for this purpose?

Can you please provide some pseudocode to demonstrate your use case?

Is there a plan to include some sort of security support, so that request.isUserInRole(String role) and request.getUserPrincipal() return some configurable values? The idea would be a @Secured annotation on test methods (with username and roles), that defines the security context of a test case.

No, there is currently no plan to support this, but it is an interesting idea.

So feel free to create a separate JIRA issue to request it.

Does all this work with a test execution listener or will it be integrated into the TestContext framework ?

The code needed to support the features in this issue will be integrated in the TestContext framework, tentatively including two SmartContextLoader implementations, a subclass of MergedContextConfiguration, a TestExecutionListener, and an annotation.

Regards,

Sam

@spring-projects-issues
Copy link
Collaborator Author

Gaetan Pitteloud commented

Hi Sam,

Thanks for the reply and explanations.

Regarding caching, I was thinking about a first test with a plain ApplicationContext and a second test with a WebApplicationContext, both defined with similar xml locations. Using current caching facility (key=xml locations), both contexts would be stored under same key.
But this is now OK as long as the context cache key contains WAC and ServletContext info.

I would argue that the lifespan of the ServletContext is tied directly to the lifespan of the corresponding WebApplicationContext. Thus if the WAC is used across multiple test classes, it should definitely be cached.

You're right, the lifespan of the ServletContext is bound to the lifespan of the WAC, not the test that uses the WAC. My first thoughts in implementating this web test feature was a test case that creates a ServletContext in @BeforeClass and then symmetrically "destroys" it in @AfterClass. But the context caching feature introduces another lifecycle that is not bound to the test lifecycle; the ServletContext is definitively part of this alternate lifecycle.

Regarding web tests without WAC, a simple TestExecutionListener can do the job :

  • before class : introspect the class to find @Autowired fields of type ServletRequest, ServletResponse, HttpSession, ServletContext, etc. (or subclasses).
  • prepare instance : creates mock request, response, etc. and injects them into @Autowired fields
  • before method: register thread local RequestContext
  • after method : reset thread local

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. With such a listener, there's no need to write a @Before method that creates all these objects. But it's not linked to WAC and thus not linked to this JIRA issue. I can write such a listener by myself.

Regarding security, I created the issue SEC-2015.

Regards,
Gaëtan

@spring-projects-issues
Copy link
Collaborator Author

spring-projects-issues commented Jul 20, 2012

Sam Brannen commented

This issue depends on #10284 in order to support common context hierarchies in Spring MVC web applications.

@spring-projects-issues
Copy link
Collaborator Author

Rossen Stoyanchev commented

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.

@spring-projects-issues
Copy link
Collaborator Author

spring-projects-issues commented Oct 7, 2012

Sam Brannen commented

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: #14496

Regards,

Sam

@spring-projects-issues
Copy link
Collaborator Author

Sam Brannen commented

Pull request submitted: #160

@spring-projects-issues
Copy link
Collaborator Author

spring-projects-issues commented Oct 7, 2012

Sam Brannen commented

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.

@spring-projects-issues
Copy link
Collaborator Author

Sam Brannen commented

Note that WebTestExecutionListener has been renamed to ServletTestExecutionListener.

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 in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

2 participants