- Published on
Spring Boot + JUnit 단위테스트
Spring Boot + JUnit 단위테스트
환경
- intelliJ
- springBoot(devtools, web-starter, lombok)
프로젝트를 생성하면 pom.xml에 기본적으로 'spring-boot-starter-test'의존성이 추가된 것을 확인할 수 있다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
기본 테스트 클래스
'src/test/java' 경로에 기본적인 테스트 클래스가 제공된다.
- @RunWith(SpringRunner.class) - Junit에서 기본으로 제공하는 러너가 아닌 스프링 러너를 사용하기 위해 추가.
- @SpringBootTest - 테스트케이스가 실행될 때 테스트에 필요한 설정과 빈들을 자동으로 초기화하는 역할을 수행.
목객체로 테스트 케이스 만들기
@WebMvcTest 사용.
웹어플리케이션에서 Controller를 테스트할 때, @WebMvcTest를 사용하거나, @AutoConfigureMockMvc를 사용한다. 두개의 어노테이션의 차이점은 @WebMvcTest은 @Controller, @RestController가 설정된 클래스를 찾아 메모리에 생성하고, @Service, @repository가 붙은 클래스는 생성되지 않는다. 반대로 @AutoConfigureMockMvc는 Controller뿐만 아니라 service, repository 모두 메모리에 올린다.
JUnit 기반의 테스트를 진행해 보기 위해 간단한 Controller와 Vo를 만든다.
//src/controller/BoardController.java
@RestController
public class BoardController {
public BoardController() {
System.out.println("=====>> board controller 생성");
}
@GetMapping("/hello")
public String hello(String name) {
return "Hello : " + name;
}
@GetMapping("/getBoard")
public BoardVo getBoard() {
BoardVo boardVo = new BoardVo();
boardVo.setSeq(1);
boardVo.setTitle("title");
boardVo.setWriter("tester");
boardVo.setContent("테스트 내용");
boardVo.setCreateDate(new Date());
boardVo.setCnt(0);
return boardVo;
}
@GetMapping("/getBoardList")
public List<BoardVo> getBoardList() {
List<BoardVo> boardVoList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
BoardVo boardVo = new BoardVo();
boardVo.setSeq(i);
boardVo.setTitle("title" + i);
boardVo.setWriter("tester" + i);
boardVo.setContent("테스트 내용" + i);
boardVo.setCreateDate(new Date());
boardVo.setCnt(0);
boardVoList.add(boardVo);
}
return boardVoList;
}
}
}
//src/domain/BoardVo.java
@Getter
@Setter
@ToString
public class BoardVo {
private int seq;
private String title;
private String writer;
private String content;
private Date createDate = new Date();
private int cnt = 0;
}
// src/test/java/BoardController.java
@RunWith(SpringRunner.class)
@WebMvcTest
public class BoardControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void testHello() throws Exception {
mockMvc.perform(get("/hello").param("name", "이름"))
.andExpect(status().isOk())
.andExpect(content().string("Hello : 이름"))
.andDo(print());
}
}
@AutoConfigureMockMvc 사용.
@SpringBootTest에는 웹어플리케이션 테스트를 지원하는 webEnvironment속성이 있다. 이속성을 생략하면 기본 값으로 WebEnvironment.MOCK이 설정되는데, 테스트 케이스 실행시 서블릿컨테이너가 구동되지 않는다.
//@AutoConfigureMockMvc 사용.
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@AutoConfigureMockMvc //클래스위에 추가되어야 한다.
public class BoardControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void testHello() throws Exception {
mockMvc.perform(get("/hello").param("name", "이름"))
.andExpect(status().isOk())
.andExpect(content().string("Hello : 이름"))
.andDo(print()); // 요청/응답 메시지를 모두 출력.
}
}
MockMvc 정리
MockMvc에서 제공하는 perform() 메서드를 사용하면 브라우저에서 URL요청을 하듯 controller를 실행할 수 있다. andExpect() 메서드를 사용하여 서버의 응답결과도 검증이 가능하다.
- perform() - org.springframework.test.web.servlet.request.MockMvcRequestBuilders의 static 메서드를 사용하여 request의 method type을 정할 수 있다. get, post, put, delete 등을 제공한다. 추가로 param(), header(), cookie() 등에 메서드로 파라미터, 헤더 쿠키등을 설정할 수 있다. perform() 메서드는 ResultActions객체를 리턴하고 ResultActions는 응답결과를 검증할 수 있는 andExpect() 메서드를 제공한다.
- andExpect() - 파라미터로 org.springframework.test.web.servlet.result.MockMvcResultMatchers에서 결과검증을 위한 다양한 static 메서드를 제공받아 테스트결과 검증에 사용할 수 있다..
- isOk() - http code 200인지 확인.
- isNotFound() - 404 Not Found인지 확인.
- isMethodNotAllowed() - 메서드불일치 405인지 확인.
- isInternalServerError() - http code 500인지 확인.
- is(int status) - 설정한 응답코드인지 확인.
내장톰캣으로 테스트.
MockMvc객체는 톰캣서버를 사용하지않고 테스트를 진행한다. 정상적으로 톰캣을 구동하여 테스트를 진행할때는, @SpringBootTest에서 webEnvironment속성값을 RANDOM_PORT나 DEFINED_PORT로 변경하여 진행한다.
@RunWith(SpringRunner.class)
//랜덤한 포트번호로 톰캣이 구동된다.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class BoardControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private TestRestTemplate restTemplate;
@Test
public void testHello() throws Exception {
String result = restTemplate.getForObject("/hello?name=이름", String.class);
assertEquals("Hello : 이름", result);
}
@Test
public void testGetBoard() throws Exception {
BoardVo boardVo = restTemplate.getForObject("/getBoard", BoardVo.class);
assertEquals("tester", boardVo.getWriter());
}
}
TestRestTemplate 객체를 사용하여 URL로 서버에 요청을 전달하고 assertEquals메서드로 응답결과를 확인.
- WebEnvironment 속성
- MOCK - 내장톰캣 구동 X. AutoConfigureMockMvc어노테이션으로 MockMvc 객체를 주입받아 테스트에 사용.
- RANDOM_POTRT - 랜덤한 포트로 내장톰캣 구동.
- DEFINED_PORT - application.properties파일에 설정된 포트를 사용.
- NONE - 서블릿기반 환경 자체를 구성하지 않음.
서비스 계층을 연동하는 컨트롤러 테스트
단순히 컨트롤러 테스트뿐만 아니라 실제 비즈니스 로직이 들어가게 되는 서비스계층을 연동하여 테스트. BoardService, BoardServiceImpl 추가.
// src/service/BoardService.java
public interface BoardService {
String hello(String name);
BoardVo getBoard();
List<BoardVo> getBoardList();
}
// src/service/BoardServiceImpl.java
@Service
public class BoardServiceImpl implements BoardService{
@Override
public String hello(String name) {
return "Hello : " + name;
}
@Override
public BoardVo getBoard() {
BoardVo board = new BoardVo();
board.setSeq(1);
board.setTitle("test title");
board.setWriter("tester");
board.setContent("test content");
board.setCreateDate(new Date());
board.setCnt(0);
return board;
}
@Override
public List<BoardVo> getBoardList() {
List<BoardVo> boardList = new ArrayList<>();
for(int i = 0; i<10; i++) {
BoardVo board = new BoardVo();
board.setSeq(i);
board.setTitle("test title" + i);
board.setWriter("tester" + i);
board.setContent(i + "번 test content");
board.setCreateDate(new Date());
board.setCnt(0);
boardList.add(board);
}
return boardList;
}
}
// 추가된 service와 연동을 위해 controller코드 수정.
@RestController
public class BoardController {
@Autowired
private BoardService boardService;
@GetMapping("/hello")
public String hello(String name) {
return boardService.hello(name);
}
@GetMapping("/getBoard")
public BoardVo getBoard() {
return boardService.getBoard();
}
@GetMapping("/getBoardList")
public List<BoardVo> getBoardList() {
return boardService.getBoardList();
}
}
// test code 수정.
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@AutoConfigureMockMvc
public class BoardControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private BoardService boardService;
@Test
public void testHello() throws Exception {
when(boardService.hello("이름")).thenReturn("Hello : 이름");
mockMvc.perform(get("/hello").param("name", "이름"))
.andExpect(status().isOk())
.andExpect(content().string("Hello : 이름"))
.andDo(print());
}
}
스프링부트 퀵스타트 (저자 채규태) 일부 내용을 정리함.