devxlogo

Simplify Unit Testing for Spring Web Components

Simplify Unit Testing for Spring Web Components

here is nothing more reassuring for the software engineer than having a set of executable unit tests that exercise all the aspects of the component under test. Traditionally, testing J2EE Web components has been a more difficult task than testing standalone Java objects because Web components must run in some form of the server platform and they are coupled to the specifics of the HTTP-based Web interaction.

Testability is one of the key principles behind the Spring framework (i.e., the ability to test each component in the framework regardless of its nature). In this sense, Spring was a major improvement over the core J2EE model, in which the components were hard to test outside of the container. Even in-container testing required significant and often complicated setup. (See Sidebar: Why Testability Is Important.)

This article describes Spring’s testability features, specifically the practical features that make unit testing Web components as easy as testing plain old Java objects (POJOs).

Introducing Spring Mock Classes

Mock object is a term originally popularized by the eXtreme Programmers and the JUnit team. In the context of unit testing, a mock object is an object that implements some well-known object interface with a “dummy” placeholder functionality. These dummy placeholder objects simulate in a very simplistic fashion the expected behavior and results of a component under test, allowing you to focus solely on the thorough testing of the component itself without worrying about other dependencies.

Spring provides a mock implementation for each key interface from the Web side of the J2EE spec:

  • MockHttpServletRequest ? You most likely will find a use for this class in every single one of your unit tests. It is the mock implementation of the most frequently used interface in J2EE Web applications: HttpServletRequest.
  • MockHttpServletResponse ? Use this object for mock implementations of the HttpServletResponse interface.
  • MockHttpSession ? This is another frequently used mock object. (This article will review use of this class for session-bound processing later.)
  • DelegatingServletInputStream ? Use this object for mock implementation of the ServletInputStream interface.
  • DelegatingServletOutputStream ? This object delegates the implementation of ServletOutputStream. It can be useful if you need to intercept and examine the content written to an output stream.

As already stated, you most likely will find the most use for mock HttpServletRequest, HttpServletResponse, and HttpSession while testing your controllers. However, Spring also provides the following mock implementations to other less frequently used components that you may find useful on their own, especially if you are an API developer:

  • MockExpressionEvaluator ? You use this mock object mostly when you intend to develop and test your own JSTL-based tag libraries.
  • MockFilterConfig ? This is a mock implementation of the FilterConfig interface.
  • MockPageContext ? This is a mock implementation of the JSP PageContext interface. You may find this object useful for testing pre-compiled JSPs.
  • MockRequestDispatcher ? This is a mock implementation of the RequestDispatcher interface. You use it mostly within other mock objects.
  • MockServletConfig ? This is a mock implementation of the ServletConfig interface. Unit testing some Web components, such as the ones the Struts framework supplies, requires you to set the ServletConfig and ServletContext interfaces implemented by MockServletContext.

So, what does it take to use these mock objects? As you know, HttpServletRequest is a component that holds immutable values that represent HTTP parameters. These parameters are what drive the functionality of the Web components. MockHttpServletRequest, which is an implementation of the HttpServletRequest interface, allows you to set these otherwise immutable parameters. In a typical Web component-testing scenario, you can instantiate this object and set any of the parameters as follows:

                     //specify the form method and the form actionMockHttpServletRequest request = new MockHttpServletRequest("GET", "/main.app");request.addParameter("choice", expanded);request.addParameter("contextMenu", "left");

Similarly, you can instantiate, fully manipulate, and examine the HttpResponse and HttpSession objects.

Now let’s see how you can make JUnit tests Spring-aware.

Spring-Specific Extensions to the JUnit Framework

Spring provides the following set of Spring-specific extensions to the JUnit framework:

  • AbstractDependencyInjectionSpringContextTests ? This is a superclass for tests, depending on the Spring context.
  • AbstractSpringContextTests ? This is a superclass for all JUnit test cases using a Spring context, and as such, it is not intended to be used directly. You will most likely end up using either AbstractDependencyInjectionSpringContextTests or subclasses of AbstractTransactionalSpringContextTests.
  • AbstractTransactionalSpringContextTests ? This is a superclass for all the tests that should occur in a transaction but that will normally roll the transaction back upon the completion of each test. You need to override onSetUpInTransaction and onTearDownInTransaction to manually initiate and/or commit the transaction (for instance, to flush the Hibernate session).
  • AbstractTransactionalDataSourceSpringContextTests ? This is a subclass of AbstractTransactionalSpringContextTests that is geared towards the use of the Spring’s JDBC-based jdbcTemplate convenience class.

All these extensions simplify the dependency injections and transaction management of the operations under test.

Common Web Testing Scenarios

This section reviews some of the common scenarios for testing Web components and how you can use Spring’s mock objects and the extensions to the JUnit framework in them.

Resolution to a Correct View

Producing the correct view based on input parameters is probably the most common function in operating a Web application. In the context of Spring MVC, this means that Spring MVC will return some ModelAndView object based on the state of the parameters.You can test this function as a regular JUnit test by simply utilizing Mock objects as follows:

                     public void final testGettingToDetails throws Exception{                                   MyController myController = new MyController();  myController.setDetailsView( detailsViewName );		  MockHttpServletRequest request = new MockHttpServletRequest();  MockHttpServletResponse response = new MockHttpServletResponse();  request.setMethod("POST");  request.addParameter("viewDetails", "true");  ModelAndView modelAndView = myController.handleRequest(request, response);  assertEquals("Incorrect view name", detailsViewName,                          modelAndView.getViewName());

Since the controller will most likely utilize some service objects to decide on the resulting view, you could also supply a custom mock service object to the controller. For more details on utilizing custom objects, reference mockobjects.com.

Session-Related Operations

Another operation that is essential to any J2EE Web application is HttpSession-bound processing. For example, Spring MVC may need to determine whether an object is in the session and what its state is. Based on that, it would produce the correct result. You can test this scenario utilizing the MockHttpSession object and JUnit framework as follows:

                     public void testInvokesCorrectMethodWithSession() throws Exception {TestController cont = new TestController();MockHttpServletRequest request = new MockHttpServletRequest("GET", "/invoiceView.app");request.setSession(new MockHttpSession(null));HttpServletResponse response = new MockHttpServletResponse();ModelAndView mv = cont.handleRequest(request, response);assertTrue("Invoked loggedIn method", cont.wasInvoked("loggedIn"));assertTrue("view name is ", mv.getViewName().equals("loggedIn"));assertTrue("Only one method invoked", cont.getInvokedMethods() == 1);                       //test the controller but without the sessionrequest = new MockHttpServletRequest("GET", "/invoiceView.app");response = new MockHttpServletResponse();		try {    cont.handleRequest(request, response);    fail("Should have rejected request without session");		}    catch (ServletException ex) {	   //This is expected}}

Forwarding and Redirecting

An operation that a Spring MVC component performs can result in forwarding or redirecting to another URL. If your goal is to examine the resulting forward or redirect, you can test that scenario by examining the MockHttpResponse object and what is in its redirecting or forwarding value as follows:

                     String responseString = ((MockHttpServletResponse)httpResponse).getForwardedUrl();		assertEquals( "Did not forward to the expected URL", responseString, expectedString );

Producing the Correct Binary Output

How many times have you had to implement the “View as PDF” functionality? Below is a snippet of JUnit code that tests exactly that function utilizing mock output stream objects:

                     		public void testPDFGeneration() throws Exception{     MockHttpServletRequest request = new MockHttpServletRequest();     MockHttpServletResponse response = new  MockHttpServletResponse();		     viewInvoiceAsPDFController.handleRequest( request, response ); 		     byte[] responsePDFValues = response.getContentAsByteArray();		     byte[] expectedPDFValues = loadBytesFromTestFile();		     assertTrue( "Did not generate expected PDF content.",                                             Arrays.equals(                                                  responsePDFValues,                                                 expectedPDFValues  ) );					}

Instead of returning the ModelAndView object, your controller ViewInvoiceAsPDFController produces binary output that you can capture as a binary array and evaluate for correctness.

Running Transaction “Wired” Unit Tests

So far, you’ve seen relatively simplistic JUnit tests that occur in the context of one controller supplied with only mock objects. But what if testing a Web component makes sense only in a transactional context (e.g., integrated with Hibernate through the dependency injection)? Spring MVC offers a decent set of extensions to the JUnit framework that do exactly that: provide injections of dependencies and transaction-safe testing (i.e., any updates are rolled back after the test completes the execution).

Testing Steps

Let’s look at a hypothetical scenario in which you would implement a test for a component (e.g., MyTransactionalController) that runs in a transactional context (i.e., the results of its method invocation are within a transaction and it should be rolled back after the test runs):

  1. Create a custom JUnit class (MyTransactionalControllerTest) that extends the Spring’s JUnit extension AbstractTransactionalSpringContextTests public class:
                         		import org.springframework.test.AbstractDependencyInjectionSpringContextTests;/** * @author begolie */public class MyTransactualControllerTest extends		AbstractTransactionalSpringContextTests {public class.
  2. In order to make Spring-managed beans visible to the Spring-aware unit test, you need to override the getConfigLocations() method and return the String array of context file locations, as follows:
                         		protected abstract  String[] getConfigLocations(){        return new String[] {"classpath:/test/spring-context.xml"};}
  3. Have a property for the classes under test, as well as associated getters and setters, because AbstractTransactionalSpringContextTests utilizes auto-wiring (a feature of the Spring framework where class dependencies are recognized by the names of the class properties and filled in with the Spring beans with the matching name or ID–See the Related Resources section for more info) and it will automatically resolve the dependencies to the classes under test as long as the class properties are named the same as the Spring-managed beans in the Spring context file and a properly named setter exists for each property under test:
                         			public MyTransactualController myTransactualController;		/**	 * @return Returns the myTransactualController.	 */	public MyTransactualController getMyTransactualController() {		return this.myTransactualController;	}	/**	 * @param myTransactualController The myTransactualController to set.	 */	public void setMyTransactualController(			MyTransactualController myTransactualController) {		this.myTransactualController = myTransactualController;	}	
  4. Implement the test method as you usually would with the “plain” JUnit tests:
                         			public void testCorrectBehavior() throws Exception{	   		//run the transactional method		myTransactualController.submitPayment( new Payment( 100 ) ); 				assertTrue( myTransactualController.isValid() );			}

    Notice that you are calling the method submitPayment, which may update the database. Spring’s JUnit extension (AbstractTransactionalSpringContextTests) will perform the automatic rollback upon the completion of this test method.

  5. If you need to perform any setup or cleanup tasks, override the AbstractTransactionalSpringContextTests’s onSetUpBeforeTransaction() or onSetUpInTransaction() methods. AbstractTransactionalSpringContextTests overrides the inherited setUp() and tearDown() methods from TestCase and makes them final.

Effective Unit Testing

Now that you’ve learned how, go ahead and start working with the Spring unit testing frameworks and Web component mock objects. Not only will you be more productive, but your software will benefit from some effective unit testing techniques as well.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist