Java Developer Tools

Spring Test Generation

CodePro can now generate tests for the Spring Framework ( www.springframework.org). Spring is a popular web framework and an alternative to J2EE. It is a lightweight, non-invasive container and makes heavy use of the dependency injection and model-view-controller patterns.

  1. Setup
  2. Hello World Example
  3. Features
    1. Built-in Spring Mock Objects
    2. Use of ModelAndView Assertions
    3. EasyMock Mock Objects for Services
    4. Annotation Based Controllers
    5. Interceptor Support
    6. Validators
  4. Detailed Example

Setup

In order to get ready to generate Spring tests, you need to configure CodePro with information about your Spring installation. Open Eclipse’s Preference dialog, and navigate to the CodePro > JUnit > Spring MVC preference page. Select Enable generation of Spring tests, then select Browse... and navigate to the location of the spring-test.jar library. This library can normally be found in the dist/modules subdirectory of your Spring Framework distribution. The library contains necessary test classes from Spring, and is automatically added to the classpath of your test projects by CodePro.

Hello World Example

Let’s start out with a simple Hello World example of a Spring controller:

public class HelloWorldController 
    implements Controller
{
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
        throws Exception
    {
        return new ModelAndView("helloWorld");
    }
}

In order to generate tests for this controller, right click on the class in the Package Explorer and choose CodePro Tools > Generate Test Cases. This will create and open a new test case called HelloWorldControllerTest.

public class HelloWorldControllerTest extends TestCase {
    /**
     * Run the ModelAndView handleRequest(HttpServletRequest,HttpServletResponse) method test.
     *
     * @generatedBy CodePro at 11/24/08 9:53 AM
     */
    public void testHandleRequest_1()
        throws Exception {
        HelloWorldController fixture = new HelloWorldController();
        MockHttpServletRequest request = new MockHttpServletRequest();
        MockHttpServletResponse response = new MockHttpServletResponse();

        ModelAndView result = fixture.handleRequest(request, response);

        // add additional test code here
        assertNotNull("ModelAndView should not be null", result);
        ModelAndViewAssert.assertViewName(result, "helloWorld");
        assertEquals(200, response.getStatus());
    }
}

Like CodePro’s normal test generation, we create a fixture of the class under test, execute the method under test, and make assertions on the results. For Spring tests, we use Spring’s existing mock objects for certain method parameters (like HttpServletRequest). We also use some Spring specific assertions on the results of the test method.

Features

Built-in Spring Mock Objects

CodePro uses Spring’s built-in mock objects when possible. Spring’s mock objects are a group of about two dozen web and portlet related mocks. They allow you to test your code in isolation of actual web services. Where CodePro sees uses of HttpServletRequest, we create fill out instances of MockHttpServletRequest. In cases where we can determine which parameters the method under test asks for from the HttpServletRequest, we’ll add those parameters to the MockHttpServletRequest.

MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("username", "testuser");
request.setParameter("forwardAction", (String) null);
request.setParameter("password", "testpass");

Similarly, if the method under test uses HttpServletResponse, we’ll create and pass in a MockHttpServletResponse instance. If the code modifies the HttpServletResponse, for instance by setting up a HTTP redirect, we’ll create an assertion for the test case with that information.

MockHttpServletResponse response = new MockHttpServletResponse();
...
assertEquals(200, response.getStatus());
assertEquals("woot", response.getRedirectedUrl());

Web mocks

  • MockExpressionEvaluator
  • MockFilterConfig
  • MockHttpServletRequest
  • MockHttpServletResponse
  • MockHttpSession
  • MockMultipartFile
  • MockMultipartHttpServletRequest
  • MockPageContext
  • MockServletConfig
  • MockServletContext

Portlet mocks

  • MockActionRequest
  • MockActionResponse
  • MockMultipartActionRequest
  • MockPortalContext
  • MockPortletConfig
  • MockPortletContext
  • MockPortletPreferences
  • MockPortletRequest
  • MockPortletResponse
  • MockPortletSession
  • MockRenderRequest
  • MockRenderResponse

Miscellaneous mocks

  • ExtendedModelMap
  • ExpectedLookupTemplate
  • SimpleNamingContext
  • SimpleNamingContextBuilder

Use of ModelAndView Assertions

CodePro’s generated tests make use of the Spring ModelAndViewAssert class for testing the ModelAndView class. This class is returned as the result of executing a Spring controller class. The ModelAndView class holds both the model and the view information; they’re contained in one object to make it possible for a controller to return both model and view in a single return value. CodePro will generate assertions for the view name, and model keys and values if there are any.

ModelAndViewAssert.assertViewName(result, "cartView");
ModelAndViewAssert.assertModelAttributeValue(result, "message", "7 items in cart");
ModelAndViewAssert.assertAndReturnModelAttributeOfType(result, "cart", Cart.class);

EasyMock Mock Objects for Services

When generating unit tests for Spring controllers, it is important to test them in isolation of the rest of the classes in the application. In Spring applications, your controller classes generally depend on other services to preform their work. These services could access databases, send emails to customers, or generally perform work that you don’t really want to do in the context of a unit test. In order to isolate your controller classes, when CodePro see them using a service it will create a mock of that service for the test. So if you have an AccountManagement interface and an AccountManagementImpl concrete implementation, instead of creating an instance of AccountManagementImpl (which would modify an actual database), CodePro will use EasyMock to create a mock implementation of AccountManagement.


    MyController fixture = new MyController();
    AccountManagement service = EasyMock.createNiceMock(AccountManagement.class);
    fixture.setAccountManagement(service);
    fixture.handleRequest(...);

These mock objects will allow any method to be called. If a method has a return type, the mock object will by default return null or 0 as appropriate. In order to return other values, the user must set up the proper expectations on the mock object, and then switch the mock over to replay mode. For example:


    MyController fixture = new MyController();
    
    AccountManagement service = EasyMock.createNiceMock(AccountManagement.class);
    EasyMock.expect(service.deleteUser("testUser")).andReturn((boolean) true);
    EasyMock.replay(service);
    
    fixture.setAccountManagement(service);
    fixture.handleRequest(...);

For more information on using EasyMock mock objects, see easymock.org.

Annotation Based Controllers

As of Spring 2.5 it is possible to define your controller classes through the use of Java annotations. CodePro recognizes controller classes that use these annotations and generates the appropriate tests. For instance, for the following controller:


@Controller
public class ImageController {

    private final ImageDatabase imageDatabase;

    @Autowired
    public ImageController(ImageDatabase imageDatabase) {
        this.imageDatabase = imageDatabase;
    }

    @RequestMapping("/imageList")
    public String showImageList(Model model1) {
        model1.addAttribute("images", this.imageDatabase.getImages());
        return "imageList";
    }

CodePro will generate the test case:


    public void testShowImageList_1()
        throws Exception {
        ImageController fixture = new ImageController(EasyMock.createNiceMock(ImageDatabase.class));
        ExtendedModelMap model1 = new ExtendedModelMap();

        String result = fixture.showImageList(model1);

        // add additional test code here
        assertEquals("imageList", result);
        assertEquals(1, model1.size());
        assertEquals(null, model1.get("images"));
    }

Both the @Controller and @RequestParam annotations are supported.

Interceptor Support

CodePro recognizes and generates tests for classes which implement the org.springframework.web.servlet.HandlerInterceptor interface.

Validators

CodePro recognizes and generates tests for classes which implement the org.springframework.validation.Validator interface.

Detailed Example

The following example demonstrates a generated test that uses EasyMock mock objects, Spring mock objects, MockHttpServletRequest parameters, and ModelAndView assertions. This example is taken from the JPetStore example from the www.springframework.org site.


public class SignonController implements Controller {

	private PetStoreFacade petStore;

	public void setPetStore(PetStoreFacade petStore) {
		this.petStore = petStore;
	}

	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
		String username = request.getParameter("username");
		String password = request.getParameter("password");
		Account account = this.petStore.getAccount(username, password);
		if (account == null) {
			return new ModelAndView("Error", "message", "Invalid username or password.  Signon failed.");
		}
		else {
			UserSession userSession = new UserSession(account);
			PagedListHolder myList = new PagedListHolder(this.petStore.getProductListByCategory(account.getFavouriteCategoryId()));
			myList.setPageSize(4);
			userSession.setMyList(myList);
			request.getSession().setAttribute("userSession", userSession);
			String forwardAction = request.getParameter("forwardAction");
			if (forwardAction != null) {
				response.sendRedirect(forwardAction);
				return null;
			}
			else {
				return new ModelAndView("index");
			}
		}
	}

}

CodePro will generate four tests for this method, one for each flow path. Here is one of those tests:


	public void testHandleRequest_1()
		throws Exception {
		SignonController fixture = new SignonController();
		fixture.setPetStore(EasyMock.createNiceMock(PetStoreFacade.class));
		MockHttpServletRequest request = new MockHttpServletRequest();
		request.setParameter("username", "testuser");
		request.setParameter("forwardAction", (String) null);
		request.setParameter("password", "testpass");
		HttpServletResponse response = new MockHttpServletResponse();

		ModelAndView result = fixture.handleRequest(request, response);

		// add additional test code here
		assertNotNull("ModelAndView should not be null", result);
		ModelAndViewAssert.assertViewName(result, "Error");
		ModelAndViewAssert.assertModelAttributeValue(result, "message", "Invalid username or password.  Signon failed.");
	}

Authentication required

You need to be signed in with Google+ to do that.

Signing you in...

Google Developers needs your permission to do that.