2010년 9월 17일 금요일

Spring에서 Controller JUnit으로 테스트하기

작성중...

Resources:
http://www.coderanch.com/t/459840/Spring/Integration-testing-Spring-Annotation-based

Controller에 대한 JUnit테스트는 조금 복잡한다.
Controller를 제외한 Repository, Service, Component stereotype들은 Dependency Injection이 복잡하지 않다. 하지만 Controller는 서블릿 컨텍스트를 초기화하여 Injection해야 하기 때문에 여러가지로 복잡한 부분이 있다.
이러한 문제 때문에 webmvc 관련된 설정은 기타 스프링 설정 파일과는 별로의 폴더에 설정한다.

아래와 같은 컨트롤러가 개발되어 있다고 가정하자.


package mypackage;

@Controller("accountController")
@RequestMapping("/accountController.sh")
public class AccountController extends BaseController {
...  
  
  @RequestMapping(params = "method=list")
  public String listAccounts(HttpServletRequest request, 
   @ModelAttribute AccountCondition condition,
   BindingResult result1, ModelMap map) throws Exception {

    
    
    map.addAttribute("accountCondition", condition);
    
    return "/account/account_lst";
  }
}


이 컨트롤러를 JUnit으로 테스트 하려면 WebContextLoader가 필요하다.
Tomcat과 같은 서블릿 컨테이너에서 테스트가 실행되는 것이 아니기 때문에 이와 비슷한 기능을 하는 Mock Web Context가 필요하다.

package mypackage;

import javax.servlet.ServletContext;

import org.springframework.beans.factory.support.BeanDefinitionReader;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.FileSystemResourceLoader;
import org.springframework.mock.web.MockServletContext;
import org.springframework.test.context.support.AbstractContextLoader;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.GenericWebApplicationContext;

public class MockWebContextLoader extends AbstractContextLoader {

  public static final ServletContext SERVLET_CONTEXT = 
    new MockServletContext("/src/main/webapp", new FileSystemResourceLoader());
  
  private final static GenericWebApplicationContext webContext =
    new GenericWebApplicationContext();
      
  protected BeanDefinitionReader createBeanDefinitionReader(
    final GenericApplicationContext context) {
    return new XmlBeanDefinitionReader(context);  
  }

  @Override
  protected String getResourceSuffix() {
    System.out.println("getResourceSuffic()");
    return ".xml";
  }

  @Override
  public ApplicationContext loadContext(String... locations) throws Exception {
    SERVLET_CONTEXT.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, webContext);
    webContext.setServletContext(SERVLET_CONTEXT);
    createBeanDefinitionReader(webContext).loadBeanDefinitions(locations);
    AnnotationConfigUtils.registerAnnotationConfigProcessors(webContext);
    webContext.refresh();
    webContext.registerShutdownHook();
    return webContext;
  }
  
  public static WebApplicationContext getInstance() {
    return webContext;
  }
}


위 WebContextLoader에서 주의 깊게 살펴 볼 메서드는 loadContext다.
maven에서 web resource를 관리하는 폴더인 src/main/webapp에서 리소스를 읽어 오도록 설정되어 있다.

컨트롤러 테스트 클래스들은 위에서 작성한 MockWebContextLoader를 사용하는 부분이 공통적으로 필요하다.
아래의 클래스는 DispatchServlet을 초기화 시키는 부분이 포함되어 있다.
또한 MockHttpServletRequest와 MockHttpServletResponse를 생성하는 메서드도 포함하고 있다.

package mypackage;

import java.util.Map;

import javax.servlet.ServletException;

import org.junit.runner.RunWith;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletConfig;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

import junit.framework.TestCase;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader=MockWebContextLoader.class, 
  locations={"classpath*:/spring/context-*.xml", 
    "classpath*:/springmvc/common-servlet.xml"})
public class AbstractControllerTestSupport extends TestCase {

  private static DispatcherServlet dispatcherServlet;
  
  @SuppressWarnings("serial")
  public static DispatcherServlet getServletInstance() {
    if(dispatcherServlet == null) {
      dispatcherServlet = new DispatcherServlet() {
        protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
          return MockWebContextLoader.getInstance();
        }
      };
      
      try {
        dispatcherServlet.init(new MockServletConfig());
      } catch(ServletException ex) {
      }
    }
    return dispatcherServlet;
  }
  
  protected MockHttpServletRequest mockRequest(String method, String uri, Map params) {
    MockHttpServletRequest req = new MockHttpServletRequest(method, uri);
    
    if(params != null) {
      for(String key: params.keySet()) {
        req.addParameter(key, params.get(key));
      }
    }
    return req;
  }
  
  protected MockHttpServletResponse mockResponse() {
    return new MockHttpServletResponse();
  }
}


이제 실제로 컨트롤러를 테스트를 작성할 차례다.

package mypackage;

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.annotation.Resource;

import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.BindingResult;
import org.springframework.validation.BindingResultUtils;

import mypackage.AccountCondition;

public class AccountControllerTest extends AbstractControllerTestSupport {

  @Resource(name="accountController")
  AccountController accountController;
  
  @Test()
  public void testListAccounts() throws Exception {
    MockHttpServletRequest request = new MockHttpServletRequest();
    MockHttpServletResponse response = new MockHttpServletResponse();
    
    
    AccountCondition condition = new AccountCondition();
//    Map map = request.getParameterMap();
    Map map = new HashMap();
    
    BeanPropertyBindingResult result1 = new BeanPropertyBindingResult(condition, "accountCondition");

    map.put(BindingResult.MODEL_KEY_PREFIX + "accountCondition", result1);
    
    condition.setQueryEmail("credemol@gmail.com");
    map.put("accountCondition", condition);

    ModelMap modelMap = new ModelMap();
    String mav = accountController.listAccounts(request, condition, result1, modelMap);
    
    Collection result = (Collection) modelMap.get("accountResult");
    
    for(Iterator it = result.iterator(); it.hasNext();  ) {
      Object obj = it.next();
      
      System.out.println("obj: " + obj);
    }
  }
}

댓글 없음:

댓글 쓰기