DigitalJoel

2011/02/05

Mock Testing Spring MVC Controller

Filed under: development, spring, testing — Tags: , , , , , , — digitaljoel @ 2:08 pm

I’m in the midst of implementing a Spring MVC based web application. We have hudson for continuous integration builds that run all of our unit tests, but testing spring MVC controllers still just isn’t quite as easy as I would hope. There is some information on testing the controllers in the official spring documentation, but for someone like me that’s not a spring guru, or just starting out, it wasn’t enough to get me going. I recently was introduced to Mockito, so I spent a bit of time today trying to get a test for our controller using Mockito. It was simple and took no time at all.

I have yet to try it in a more complex controller method, but I think it’ll work just fine , especially when I get some utilities in place to initialize the mock objects that are commonly used by the controller. As it stands, here’s what I did to get it going. This tests the controller as a POJO, without using any spring configuration or capabilities.

Here is the controller I wish to test. Obviously I stripped out a bunch of code not needed for this demonstration.

@Controller
@RequestMapping( "/admin/survey" )
public class SurveyAdminController
{
    
    @Resource
    private SurveyService surveyService;
    
    @Resource
    private UnitService unitService;
    
    @Resource
    private OrganizationService orgService;
    
    /**
     * Show the table of existing questions to the user
     * @param model
     * @return
     */
    @RequestMapping( "questions" )
    public ModelAndView listQuestions()
    {
        ModelAndView mav = new ModelAndView( "/admin/questionList");
        List<SurveyQuestion> questions = surveyService.findAllQuestions();
        mav.addObject( "questions", questions );
        List<UnitFeature> features = unitService.getAllFeatures();
        mav.addObject( "features", features );
        List<Organization> organizations = orgService.getAll();
        mav.addObject( "organizations", organizations );
        return mav;
    }
}

For this simple test, it’s just going to validate that the view name is correct, and that the model that’s returned contains the correct information.


package com.bi.controller;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.when;

import java.util.ArrayList;
import java.util.List;

import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.servlet.ModelAndView;

import com.bi.data.OrganizationService;
import com.bi.data.SurveyService;
import com.bi.data.UnitService;
import com.bi.data.corp.Organization;
import com.bi.data.survey.SurveyQuestion;
import com.bi.data.unit.UnitFeature;
import com.bi.web.util.MessageUtil;

public class SurveyAdminControllerTest
{
    @Mock SurveyService surveyService;
    @Mock UnitService unitService;
    @Mock OrganizationService orgService;
    @Mock MessageUtil messageUtil;

    @Before
    public void setup()
    {
        // this must be called for the @Mock annotations above to be processed.
        MockitoAnnotations.initMocks( this );
    }
    
    @Test
    public void testListQuestions()
    {
        // setup our mock question list
        List<SurveyQuestion> questions = new ArrayList<SurveyQuestion>();
        questions.add( new SurveyQuestion( "asdf", null ));
        when( surveyService.findAllQuestions()).thenReturn( questions );

        // setup our mock feature list
        List<UnitFeature> features = new ArrayList<UnitFeature>();
        features.add( new UnitFeature( "TEST FEATURE" ));
        when( unitService.getAllFeatures()).thenReturn( features );

        // setup our mock organization list
        List<Organization> orgs = new ArrayList<Organization>();
        orgs.add( new Organization( "TEST ORGANIZATION" ));
        when( orgService.getAll()).thenReturn( orgs );

        // create an instance of the controller we want to test
        SurveyAdminController controller = new SurveyAdminController();

        // since we aren't using spring, these values won't be injected, so set them manually
        ReflectionTestUtils.setField( controller, "surveyService", surveyService );
        ReflectionTestUtils.setField( controller, "unitService", unitService );
        ReflectionTestUtils.setField( controller, "orgService", orgService );

        // call the method under test
        ModelAndView mav = controller.listQuestions();

        // review the results.
        assertEquals( questions, mav.getModel().get( "questions" ));
        assertEquals( features, mav.getModel().get( "features" ));
        assertEquals( orgs, mav.getModel().get( "organizations" ));
        assertEquals( "/admin/questionList", mav.getViewName());
    }
}

The comments should be pretty self explanatory in the test class. The awesomeness of Mockito is how you setup the mocks. A line like this:

        when( unitService.getAllFeatures()).thenReturn( features );

reads very nicely and sets up the return value for my service object, which keeps me from needing a database or anything setup in order to test my controller method. Mockito ftw.

About these ads

6 Comments »

  1. Hi, I am trying to use mockito, to keep service class from using db. I am getting an error “Could not find field [as] on target [ebedynky.controllers.AdministratorController@1543cc88]

    Here is the code of test class

    public class AdministratorControllerTest {

    AdministratorController controller = new AdministratorController();
    ;
    @Mock
    AdministratorService as;

    /**
    * Test of getAdminZone method, of class AdministratorController.
    */
    @Test
    public void testGetAdminZone() {
    System.out.println("getAdminZone");
    String expResult = "admin/adminZone";
    String result = controller.getAdminZone(new ExtendedModelMap());
    assertEquals(expResult, result);
    }

    /**
    * Test of getNewRegistrations method, of class AdministratorController.
    */
    @Test
    public void testGetNewRegistrations() throws Exception {
    System.out.println("getNewRegistrations");
    List users = new ArrayList();
    User user = new User();
    user.setID(1);
    user.setAccountState(false);
    users.add(user);

    MockitoAnnotations.initMocks(this);
    when(as.getAllDenied()).thenReturn(users);
    ReflectionTestUtils.setField(controller, "as", as);
    ModelAndView mav = controller.getNewRegistrations();

    }
    }

    and here’s the controller class


    @Controller
    public class AdministratorController {

    /**
    * Fetches the AdminZone page when requested.
    * @param model data holder for this view
    * @return string representing page location
    */
    @RequestMapping(value = "/adminZone", method = RequestMethod.GET)
    public String getAdminZone(Model model) {
    return "admin/adminZone";
    }

    /**
    * Fetches the NewRegistrations page when requested.
    * @return model and view representing page location and model
    */
    @RequestMapping(value = "/newRegistrations", method = RequestMethod.GET)
    public ModelAndView getNewRegistrations() {
    AdministratorService as = new AdministratorService();
    Map model = new HashMap();
    model.put("users", as.getAllDenied());
    return new ModelAndView("user/allUsers", model);
    }
    }

    Comment by Milan Cernil — 2011/05/17 @ 6:27 am

  2. Oh, just a quick correction of question. I’ve just figured out, that I had needed to put AdministratorService as a global variable. But I have another problem. When I use assertEquals, I get null from getModel().get(users). The problem is obvious – even if I am using the mock objects, the hibernate still executes SQL query to db. Do you know where error/fault could be? Thank you very much

    Comment by Milan Cernil — 2011/05/17 @ 6:46 am

    • In your getNewRegistrations method you have the line
      AdministratorService as = new AdministratorService();
      so the controller is going to ignore the mocked one you setup in your test and just run the normal code path.

      Comment by digitaljoel — 2011/05/17 @ 9:13 am

  3. Hi Joel,

    Great example above, very insightful. I was wondering if you have used Mockito in concert with a Controller that needs something from the ApplicationContext. Does Mockito work in concert with the @ContextConfiguration annotation?

    Thanks, Tom

    Comment by Tom Hickerson — 2011/07/07 @ 10:09 am

    • Hi Tom,
      In the test above, since I’m testing the controller as a plain POJO, it’s not doing any spring configuration, including the context. But, you should be able to mock out any beans you need. As you can see, in my controller, all of the services are usually injected using the @Resource annotation. They are all spring beans that are loaded into the context. In the test, because the @Resource isn’t processed, I use the following to set it manually to my mocked out instance, where I’ve mocked the methods that I know will be called.

      // since we aren’t using spring, these values won’t be injected, so set them manually
      ReflectionTestUtils.setField( controller, “surveyService”, surveyService );
      ReflectionTestUtils.setField( controller, “unitService”, unitService );
      ReflectionTestUtils.setField( controller, “orgService”, orgService );

      Does that explanation help?

      Comment by digitaljoel — 2011/07/08 @ 10:30 am

  4. It’s very helpful to me. Thanks. :)

    Comment by 김제준 — 2011/08/22 @ 9:17 pm


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

The Silver is the New Black Theme Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.

Join 224 other followers