Testing Your Controllers

One of the reasons for setting up this page was I wanted to write something somewhere about the new testing techniques I've been using over the past few days. I'm working on a Java web application, using a fairly standard MVC structure. Working on an XP team, I've always felt a little uneasy about not having a good way to build controllers test first. Typically in past projects I've tested controllers indirectly by using tools like HttpUnit to interact with the web front end, and exercise the view and controller components together. This approach has a number of drawbacks. In the case of a failure, it is difficult to determine whether the bug is in the view or the controller (or even the model). It is also necessary to develop the view and the controller in tandem, as one cannot be tested without the other. On my current project, we are using Spring to provide the MVC framework. This allows us to make use of some of the other facilities provided by Spring to enhance our testing. Spring provides support for unit testing controllers independently. This has a number of advantages:

  • We can develop controllers test-first.
  • Breakages in the unit tests point explicitly to problems in the controller, rather than problems in one of the combination of Model, View and Controller.
  • We can run the unit test outside of the container, meaning that we do not have to deploy within each code/test cycle.
  • The tests themselves run faster.
  • We can develop and test all of our controller code before developing any of the view components.
  • We can develop and test all of our controller code before creating the Spring application-context.xml, which makes the requirements for the contents of this file much clearer.

The difficulty we had had with unit testing controllers previously was that the methods in their public interface tended to take an HttpServletRequest and HttpServletResponse as parameters, and operate on these. When running a unit test, such objects are not normally to hand, and are difficult to construct. Spring aids this situation by providing the classes MockHttpServletRequest and MockHttpServletResponse in spring-mock.jar (note that these classes are not part of the core Spring jar). Here is an example of how we can use them in a JUnit test case.

import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.servlet.ModelAndView;

public MyControllerTest extends TestCase {

public void testGettingIndexPage() throws Exception {

MyController controller = new MyController();
controller.setIndexView("index.jsp");

MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletRequest();

request.setMethod("GET");

ModelAndView modelAndView = controller.handleRequest(request, response);

assertEquals("Should get index page", modelAndView.getViewName(), "index.jsp");
}
}

This test shows that we intend our controller to respond to requests on the base url to display index.jsp. By writing the test first, we can see what we need to implement in our controller. To make this test pass requires only a very simple controller (which will be a subclass of one of the Spring MVC controllers).


import org.springframework.web.servlet.mvc.AbstractController;

public class MyController extends AbstractController {

private String indexView;

public void setIndexView(String viewName) { indexView = viewName; }
public String getIndexView() { return indexView; }

public ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) {
return new ModelAndView(getIndexView());
}
}

To make things a bit more interesting, we can add a test that passes in some parameters on the request, and expects an appropriate response, for example passing in a search query and expecting a results page back. Adding to our test case, we can write:

     public void testSearching() throws Exception {          MyController controller = new MyController();         controller.setSearchResultsView("results.jsp");         MockHttpServletRequest request = new MockHttpServletRequest();         MockHttpServletResponse response = new MockHttpServletResponse();         request.setMethod("GET");          request.addParameter("query","testing");          ModelAndView modelAndView = controller.handleRequest(request, response);          assertEquals("Should get results page", modelAndView.getViewName(), "results.jsp");             }

Again the intention of the test is pretty clear (we can now see that our testcase might benefit from factoring some common setup into a setup method, but we'll leave that for now), and again, we don't need to add much to our controller to make it pass.

 

public class MyController extends AbstractController {

// in addition to the fields and accessors from before…
private String searchResultsView;

public void setResultsView(String viewName) { searchResultsView = viewName; }
public String getSearchResultsView() { return searchResultsView; }

public ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) {

String query = request.getParameter("query");
if ( query != null ) {
// perform the search
return new ModelAndView(getSearchResultsView());
} else {
return new ModelAndView(getIndexView());
}
}
}

This shows a couple of very simple iterations of writing tests and implementation for a controller, you can go a lot further, making both the tests and the implementation more sophisticated. The main point is that we have built and tested the controller in a way that allows quick iteration, promotes clarity and good test coverage, and does not require a view component to be written to do so.

Unit Testing with Junit, Spring and Hibernate – Part 2

This is a continuation from my previous post on Spring and MockStrutsTestCase and focus on testing Hibernate DAO’s. As I mentioned in the previous post, Spring framework provides a nifty base class (AbstractTransactionalDataSourceSpringContextTests) that provides automatic transaction rollback, exposing a JDBC template to interact with the DB and auto wiring of beans.

Lets take a simple DAO class that save a User object to the database:

public class UserDaoImpl extends HibernateDaoSupport implements UserDao {
  public void save(User user) {
      getHibernateTemplate().save(user);
  }
}

Now in order to test this you would write a test class as below extending from AbstractTransactionalDataSourceSpringContextTests class.

public class UserDaoTest extends AbstractTransactionalDataSourceSpringContextTests {
  private UserDao userDao;
  private SessionFactory sessionFactory = null;

  protected String[] getConfigLocations() {
      return new String[]{"test-spring-config.xml"};
  }

  /**
   * Spring will automatically inject UserDao object on startup
   * @param userDao
   */
  public void setUserDao(UserDao userDao) {
      this.userDao = userDao;
  }

  /**
   * Spring will automatically inject the Hibernate session factory on startup
   * @param sessionFactory
   */
  public void setSessionFactory(SessionFactory sessionFactory) {
      this.sessionFactory = sessionFactory;
  }

  /**
   * Test the save method
   *
   */
  public void testSave(){
      String query = "select count(*) from user where first_name = 'Firstname'";
      int count = jdbcTemplate.queryForInt(query);
      assertEquals("A user already exists in the DB", 0, count);

      User user = new User();
      user.setFirstName("Firstname");

      userDao.saveUser(user);

      // flush the session so we can get the record using JDBC template
      SessionFactoryUtils.getSession(sessionFactory, false).flush();

      count = jdbcTemplate.queryForInt(query);
      assertEquals("User was not found in the DB", 1, count);
  }
}

The test class has to implement the protected String[] getConfigLocations() method from the base class and return a String array of Spring config files which will be used to initialize the Spring context.

UserDao and SessionFactory properties are defined with the setter methods and the base class will take care of injecting them automatically from the Spring context. Auto wiring will not work if there are multiple objects implementing the same interface. In such a case you can remove the setter method and retrieve the object using the exposed applicationContext as below.

   /**
   * Overridden method from base class which gets called automatically
   */
  protected void onSetUpBeforeTransaction() throws Exception {
      super.onSetUpBeforeTransaction();
      userDao = (UserDao) applicationContext.getBean("userDao");
  }

The base class also exposes a JDBC template object (jdbcTemplate) that can be used to query data or setup test data in the database. Note that you need to have a data source and a transaction manager defined in your Spring config in order to use the AbstractTransactionalDataSourceSpringContextTests base class. The data source defined in the config file will be bound to the exposed JDBC template.

In the testSave method first we verify there is no record in the User table where first name equals to ‘Firstname’ using the jdbc template object. Then we call the save method on the UserDao passing it a User object.

Now we simple verify there is a record in the table where first name equals to ‘Firstname’. Before running the query we flush the current Hibernate session to make sure jdbcTemplate can see the newly added record.

Thats it and when the testSave method exits the current transaction will be rolled back and the record inserted to the User table will not be saved. This is great as your test database will always be at a know state at the start and end of a test method.

The spring config file will like below (test-spring-config.xml) :

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>  
    <bean name="userDao" class="com.dao.UserDaoImpl">
       <property name="sessionFactory">
           <ref bean="sessionFactory"/>
       </property>
    </bean>

    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
       <property name="dataSource">
           <ref bean="dataSource"/>
       </property>
       <property name="mappingResources">
           <list>
               <value>hibernates/User.hbm.xml</value>
           </list>
       </property>
       <property name="hibernateProperties">
           <props>
               <prop key="hibernate.query.factory_class">org.hibernate.hql.classic.ClassicQueryTranslatorFactory</prop>
               <prop key="hibernate.dialect">org.hibernate.dialect.OracleDialect</prop>
           </props>
      </property>
    </bean>

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
       <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
       <property name="url" value="jdbc:oracle:thin:@localhost:1521:ORCL" />
       <property name="username" value="test" />
       <property name="password" value="test" />
    </bean>

    <bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
       <property name="sessionFactory"><ref local="sessionFactory"/></property>
    </bean>

</beans>

Achieve Automatic Transaction Rollback with MockStrutsTestCase and Spring

Spring provides a convenience base class (AbstractTransactionalDataSourceSpringContextTests which extends the TestCase class from Junit) to automatically rollback any updates made to the database at the end of a test method. This works great when integration testing Service and DAO beans but it wont help when integration testing the Struts layer with MockStrutsTestCase.

Spring does not provide a class such as AbstractTransactionalDataSourceSpringContextTests that extends from the MockStrutsTestCase. This might be due to practical issues with the way Spring context initialization works when using MockStrutsTestCase. I tried achieving the transaction rollback in the same way as was done in the AbstractTransactionalDataSourceSpringContextTests class but it didn’t work out as the application code started a new transaction and commited as usual without using the transaction I started in my own subclass of MockStrutsTestCase.

Another option left for me was to go down to the Transaction Manager level and achieve the rollbacks there. The application uses Hibernate at the DAO level and it was using HibernateTransactionManager as the transaction manager implementation when running test cases . Therefore I had to write a new class extending from HibernateTransactionManager which overrides the doCommit() method. The overridden method will call doRollback() method in the super class. This would rollback the current running transaction even if the declarative transaction handling code in Spring calls doCommit on the transaction manager.

package com.xyz.txn;import org.springframework.orm.hibernate3.HibernateTransactionManager;

public class HibernateRollbackTxnManager extends HibernateTransactionManager {

/*

  * doCommit method that calls the doRollback method of the super class.

  *

  */

 protected void doCommit(DefaultTransactionStatus txnStatus) {

     super.doRollback(txnStatus);

 }

}

Finally override the default transaction manager in your test spring bean configuration with the rollback only implementation.

  <bean id="txnManager" class="com.com.xyz.txn.HibernateRollbackTxnManager">

      <property name="sessionFactory"><ref local="sessionFactory"></property>

  </bean>

The same strategy would work with other transaction managers such as DataSourceTransactionManager too.