본문 바로가기

Java & Spring

[java] Spring boot 손쉬운 test 코드 작성하기


spring에서는 3.2버전부터 MockMvc를 지원하여 매우 편리하게 컨트롤러 테스트 코드를 작성할 수 있었다.


spring boot에서는 spring-boot-starter-test 모듈에 일반적으로 spring-test 모듈과 같이 사용하는 hamcrest, mokito-core, json-path등의 라이브러리가 같이 모듈화 되어 조금 더 편리하게 테스트를 할 수 있다.


이번 포스팅에서는 간단한 상품 정보를 조회하는 데모 어플리케이션을 이용하여 스프링 부트에서 테스트를 진행하는 방법을 알아보겠다.


먼저 메이븐을 사용한다면 pom.xml에 spring-boot-starter-test 모듈을 추가하자. 

spring-boot-starter-test 모듈에는 spring-test모듈 외에 hamcrest, ockito-core, json-path등의 테스트에 유용한 라이브러리가 미리 포함되어 있다.


<메이븐 디펜던시>

		
			org.springframework.boot
			spring-boot-starter-test
			test
		


아래는 컨트롤러 코드이다. url path에 상품id를 넣어 상품정보를 조회한다.

<컨트롤러 코드>

package com.example.demo.controller;

import com.example.demo.model.Product;
import com.example.demo.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/")
public class ProductController {

    @Autowired
    private ProductService productService;

    @RequestMapping("/products/{productId}")
    public Product getProduct(@PathVariable(value = "productId") Long productId){
        return productService.getProduct(productId);
    }
}


서비스 코드에서는 파라미터로 넘어온 id에 대한 상품을 반환한다. 데모코드이기 때문에 임의로 Product 객체를 생성하여 반환한다.

<서비스 코드>

package com.example.demo.service;

import com.example.demo.model.Product;
import org.springframework.stereotype.Service;

@Service
public class ProductService {

    public Product getProduct(Long productId) {
        Product product = new Product();
        product.setProductId(productId);
        product.setProductName("과자");
        product.setDescription("맛있는 과자");
        product.setPrice(105.5);
        return product;
    }
}


아래는 Product에 해당하는 모델 객체이다. 상품id, 이름, 설명, 가격필드를 가지고 있다.


<모델 코드>

package com.example.demo.model;

public class Product {
    private Long productId;
    private String productName;
    private String description;
    private double price;

    public Long getProductId() {
        return productId;
    }

    public void setProductId(Long productId) {
        this.productId = productId;
    }

    public String getProductName() {
        return productName;
    }

    public void setProductName(String productName) {
        this.productName = productName;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }
}


다음은 테스트 코드를 작성해보겠다.

package com.example.demo;

import com.example.demo.controller.ProductController;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoTestApplicationTests {

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    @Before
    public void setUp() {
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
    }

    @Test
    public void testGetProduct() throws Exception {
        mockMvc.perform(get("/products/1")
                .accept(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(handler().handlerType(ProductController.class))
                .andExpect(handler().methodName("getProduct"))
                .andExpect(jsonPath("$.productId", is(1)))
                .andExpect(jsonPath("$.productName", is("과자")))
                .andExpect(jsonPath("$.price", is(105.5)))
                .andDo(MockMvcResultHandlers.print());
    }
}


위의 테스트 코드를 간단하게 설명하면 다음과 같다.

첫번째로 junit의 @Before 어노테이션을 이용하여 mockMvc 객체를 생성한다.

그리고 @Test 어노테이션이 달려있는 메소드에서 실제 실행할 테스트를 작성한다.


각각의 메소드를 설명하면 아래와 같다.

mockMvc.perform : 실제 요청을 수행한다.

get("products/1") : 호출할 URL를 기술한다.

andExpect : 요청에 대한 각종 기대값을 설정한다.

andDo : 요청에 대한 처리를 진행한다.


위의 코드에서는 get 요청을 하기 위한 url을 설정하고 실행되는 컨트롤러 클래스와 메소드 그리고 jsonPath 라이브러리를 이용하여 결과 값에 대한 검증을 수행하고 있다.

그리고 마지막으로 andDo 메소드에서는 요청에 대한 처리를 진행한다. print 메소드를 이용하여 결과를 출력한다.


<테스트 수행 결과>

MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /products/1
       Parameters = {}
          Headers = {Accept=[application/json]}

Handler:
             Type = com.example.demo.controller.ProductController
           Method = public com.example.demo.model.Product com.example.demo.controller.ProductController.getProduct(java.lang.Long)

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = {Content-Type=[application/json;charset=UTF-8]}
     Content type = application/json;charset=UTF-8
             Body = {"productId":1,"productName":"과자","description":"맛있는 과자","price":105.5}
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

수행 결과를 보면 기대한 값에 맞게 요청이 처리된 점을 확인 할 수 있고 그 외에 자세한 호출 정보를 확인 할 수 있다.