본문 바로가기

NAVER D2 정리/Springframework

Spring-Test-MVC 프로젝트 소개

원문 작성일: 2011.12.22

원문 : https://d2.naver.com/helloworld/1341

 

 

이 글에서는 Builder 패턴을 사용해서 스프링 MVC 테스트 코드를 작성하는 방법을 알아본다.

 

Spring-Test-MVC 프로젝트는 무엇일까

Spring-Test-MVC 프로젝트의 목표는 Servlet 컨테이너를 사용하지 않아도 MockHttpServletRequest와 MockHttpServletResponse를 사용해서 Spring Controller를 쉽고 편하게 테스트하는 방법을 제공하는 것이다.

 

하지만 안타깝게도 이 프로젝트의 홈페이지에서 살펴본 테스트 코드를 실무에 그대로 적용할 수는 없다.

분명히 NullPointerException이 발생할 것이기 때문이다.

Controller에서 참조하는 모든 객체 레퍼런스가 null이기 때문이다.

물론, 다른 의존성이 없는 아주 독립적인 Controller라면 무사히 테스트할 수 있겠지만, 그런 경우는 드물 것이다.

 

Spring TestContext

평범한 계층 구조 아키텍처를 사용한다면 보통은 다음과 같은 구조로 Controller와 Service, DAO(Data Access Object)가 연결되어 있다.

계층 구조의 객체 의존성

Controller에서는 Service를 사용하고 그 Service에서는 다시 DAO를 사용한다.

이런 상황에서 Controller를 테스트할 때에는 테스트하는 범위를 크게 두 가지로 나눌 수 있다.

  • Controller 클래스 단위 테스트
  • Controller 클래스 통합 테스트

Controller 클래스 단위 테스트는 비교적 간단하다.

테스트하려는 Controller가 참조하는 모든 객체의 Mock 객체를 만들어서 컨트롤러에 주입하고 테스트하면 된다.

 

Conroller 클래스 통합 테스트는 조금 복잡해진다.

Spring Bean 설정을 사용해서 테스트용 ApplicationContext를 만들어 Bean 주입 기능을 사용해야 한다.

물론 Spring에는 TestContext라는 기능으로 그런 작업을 지원할 뿐만 아니라,

그보다 더 중요한 기능으로 테스트용 ApplicationContext 공유 기능을 제공한다.

 

예를 들어, 테스트 클래스가 TestClassA, TestClassB, TestClassC와 같이 세 개가 있다고 가정하자.

만약 이 세 테스트 클래스에서 사용하는 Bean 설정이 모두 같다면 테스트할 때마다 ApplicationContext를 새로 만드는 것은 불필요한 작업일 뿐 아니라 테스트 성능에 많은 부하를 줄 수 있다.

그렇기 때문에 테스트 클래스에서 동일한 Bean 설정 파일을 사용한다면, ApplicationContext를 한 번만 만들어서 TextContext에 캐시하여 사용한다.

 

TestContext와 ApplicationContext

SpringFramework에서 기본으로 제공하는 SpringJUnit4ClassRunner를 사용하여 다음 예제와 같이 ApplicationContext 공유 기능을 사용할 수 있다.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/testContext.xml")
public class TestClassA {
	// 테스트 코드
}

@RunWith 메소드는 JUnit에서 제공하는 테스트 러너 확장 지점이고,

그 확장 지점을 사용해서 TestContext 기능을 사용하도록 Spring이 SpringJUnit4ClassRunner를 제공한다.

그리고 SpringJUnit4ClassRunner는 @ContextConfiguration에 설정한 설정으로,

테스트에 사용할 ApplicationContext를 만들고 Bean을 관리한다.

이렇게 하면 테스트에서는 @Autowired나 @Inject를 사용해서 테스트할 Bean을 주입받아 사용할 수 있다.

 

Spring-Test-MVC와 TestContext 연동

그럼 Spring-Test-MVC 프로젝트를 TestController 기능과 어떻게 연동할 수 있을까?

 

우선, 테스트에서 공통으로 사용할 다음과 같은 Bean 설정 파일이 있다고 가정하자.

파일 이름은 "ApplicationContextSetupTests-context.xml"이다.

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xsi:schemaLocation="
         http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
         http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
         

    <annotation-driven />
    
    
    <beans:bean id="testController"
        class="org.springframework.test.web.server.setup.ApplicationContextSetupTests$TestController"/>
        
        
</beans:beans>

mvc 네임스페이스를 기본 네임스페이스로 사용해서 어노테이션 기반 Spring MVC에 필요한 Bean을 등록하고,

Controller 클래스를 하나 등록했다.

테스트 코드와 테스트용 Controller 코드는 다음과 같다.

 

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class ApplicationContextSetupTests {

    @Autowired
    ApplicationContext context;
    
    @Test
    public void responseBodyHandler() {
        MockMvc mockMvc = MockMvcBuilders.applicationContextMvcSetup(context)
                .configureWarRootDir("src/test/webapp", false).build();
        mockMvc.perform(get("/form"))
            .andExpect(status().isOk())
            .andExpect(status().string("hello"));
        mockMvc.perform(get("/wrong"))
            .andExpect(status().isNotFound());
    }
    
    @Controller
    static class TestController {
        @RequestMapping("/form")
        public @ResponseBody String form() {
            return "hello";
        }
    }
}

이 테스트 클래스에 들어있는 TestController 클래스가 위의 Spring 설정 파일에 Bean으로 등록한 Controller다. "/form"이라는 URL로 들어오는 요청을 public String form() 메소드가 처리하도록 매핑하고 결과로 "hello"라는 메시지를 반환한다.

 

이 Controller를 테스트하는 코드를 하나씩 살펴보자.

 

[TestContext 설정]

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration

위의 두 줄은 앞서 말했듯이 Spring의 TestContext 기능을 사용하는데 필요하다.

다만, 다른 점은 @ContextConfiguration에 Bean 설정 파일의 이름을 입력하지 않았다는 것이다.

Bean 설정 파일의 이름을 입력하지 않으면 테스트 클래스 이름과 "-context.xml"로 이루어진 설정 파일을 찾아서 Bean 설정 파일로 사용한다.

즉, 위에서 만든 "ApplicationContextSetupTests-context.xml" 파일을 Bean 설정 파일로 사용하게 된다.

 

[ApplicationContext 주입]

@Autowired
ApplicationContext context;

그런 다음, 테스트에서 사용하는 ApplicationContext를 주입 받는다.

여기서 ApplicationContext를 주입받는 이유는 MockMvc 객체를 만들 때 필요하기 때문이다.

 

[테스트 메소드 작성]

@Test
public void responseBodyHandler() {
    ...
}

이제 테스트 메소드를 만들고 테스트를 시작한다.

우선 아래와 같이 MockMvc 객체를 만들어야 한다.

MockMvc mockMvc = MockMvcBuilders
                      .applicationContextMvcSetup(context)
                      .configureWarRootDir("src/test/webapp", false)
                      .build();

여기서는 TestContext의 ApplicationContext를 사용해서 MockMvc 객체를 만들었다.

하지만, SpringSource에서 제공하는 Spring-Test-MVC는

ApplicationContext 타입의 객체로 MockMvc 객체를 만드는 메소드를 제공하지 않는다.

WebApplicationContext 타입의 객체로 MockMvc 객체를 만들도록 할 뿐이다.

 

여기서는 TestContext는 그대로 유지한 채 Spring-Test-MVC를 확장하는 방법을 사용했다.

Spring-Test-MVC에서 MockMVC 객체를 ApplicationContext로도 생성할 수 있도록 코드를 추가한 것이다.

 

하지만 이러한 방법 외에 TestContext를 확장하는 방법도 있다.

즉, TestContext에서 ApplicationContext가 아닌 WebApplicationContext를 생성하도록 확장할 수 있다.

다음의 코드가 TestContext를 확장한 예이다.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
        loader=TestGenericWebXmlContextLoader.class,
        location={"/org/springframework/test/web/server/sample/servlet-context.xml"})
public class TestContextTests {

    @Autowired
    private WebApplicationContext wac;
    
    private MockMvc mockMvc;
    
    @Before
    public void setup() {
        this.mockMvc = MockMvcBuilders.webApplicationContextSetup(this.wac).build();
    }
    
    @Test
    public void tilesDefinitions() throws Exception {
        this.mockMvc.perform(get("/"))
                .andExpect(status().isOk())
                .andExpect(forwardedUrl("/WEB-INF/layouts/standardLayout.jsp"));
    }
}

class TestGenericWebXmlContextLoader extends GenericWebXmlContextLoader {
    public TestGenericWebXmlContextLoader() {
        super("src/test/resources/META-INF/web-resources", false);
    }
}

@ContextConfiguration에서 loader 속성을 확장하여 WebApplicationContext를 생성하는 Loader를 설정하면,

TestContext에서 ApplicationContext가 아닌 WebApplicationContext를 생성한다.

따라서, 이 WebApplicationContext를 @Autowired로 주입받아서 Spring-Test-MVC에서 사용하면 Spring-Test-MVC는 아무것도 수정하지 않아도 된다.

 

실제 테스트 코드는 단순하다.

mockMvc.perform(get("/form"))
    .andExpect(status().isOk())
    .andExpect(content().string("hello"));

실제로 SpringSource의 Spring-Test-MVC 프로젝트에는 isOk()라는 메소드가 없었다.

대신 status(HttpStatus) 메소드를 제공해서 status(HttpStatus.OK)처럼 Enum을 사용해서 테스트할 수 있었다.

하지만 Enum보다는 위와 같이 isXXX() 류의 메소드를 제공하는 것이 편하다고 생각해서 코드를 추가했다.

 

그리고 Controller라 처리할 수 없는 "/wrong" 요청을 보내서 status().isNotFound()를 확인했다.

mockMvc.perform(get("/wrong"))
            .andExpect(status().isNotFound());