Spring Boot에서 JUnit5를 이용해 테스트 코드를 작성해보기 전에 기본적인 내용에 대해서 다루고 넘어갈 것이다. 일반적으로 단위 테스트(Unit Test) 코드를 작성할 때 5가지 원칙을 강조한다.
Fast : 테스트 코드를 실행하는 일은 오래 걸리면 안된다.
Independent : 독립적으로 실행이 되어야한다.
Repeatable : 반복 가능해야한다.
Self Validation : 메뉴얼 없이 테스트 코드만 실행해도 성공, 실패 여부를 알 수 있어야한다.
Timely : 바로 사용 가능해야한다.
Junit
Junit은 Java의 단위 테스팅 도구이다.
단위 테스트 Framework중 하나
단정문으로 Test Case 수행결과를 판별한다.
Annotation으로 간결하게 사용가능하다.
Dependencies
spring boot 2.2.0 이후 버전에서는 Junit5가 기본으로 변경되었다. Junit5는 Java8 부터 지원하며, 이전 버전으로 작성된 테스트 코드여도 컴파일이 지원된다.
SpringBoot 2.2.0 이전 버전에서 junit5 설정
maven
<!-- spring boot test junit5 사용 exclusion을 통해 junit4에서 코드 실행시 사용하는 vintage-engine 예외처리--><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions></dependency><!-- 테스트 코드 작성에 필요한 junit-jupiter-api 모듈과 테스트 실행을 위한 junit-jupiter-engine 모듈 포함 --><dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId></dependency>
여기서 @Service은 @WebMvcTest의 적용 대상이 아니다. BookService 인터페이슬르 구현한 구현체는 없지만, @MockBean을 적극적으로 활용해 컨트롤러 내부의 의존성 요소를 각자 객체로 대체 했다. MockBean은 실제 객체는 아니지만 실제 객체처럼 동작하게 만들 수 있다.
given(bookService.getBookList()).willReturn(Collections.singletonList(book)); : 가짜 객체를 만들어 given()을 사용해 메서드 실행에 대한 반환값을 미리 설정했다.
그후 andExpect()로 예상 값이 나오는지에 대해서 테스트를 진행했다.
만약 기본설정만 필요하다면 @AutoConfigureMockMvc 으로 간단하게 설정할 수 있다.
하지만 AutoConfig로 설정을 하면 Custom하기 어려워진다. 아래 코드는 MockMvcBuilders를 활용해 MockMvc 인스턴스를 생성해주었다. 이렇게 공통 추상클래스를 만들어 사용할 수 있다.
/** * 테스트시 필요한 커스텀 공통 설정 추상 클래스 */@SpringBootTestpublicabstractclassAbstractControllerTest {protectedMockMvc mockMvc;abstractprotectedObjectcontroller(); @BeforeEachprivatevoidsetup() { mockMvc =MockMvcBuilders.standaloneSetup(controller()) // 기본설정 .addFilter(newCharacterEncodingFilter(StandardCharsets.UTF_8.name(),true)) // 테스트 수행시 한글 깨짐 방지.alwaysDo(print()) // 항상 콘솔 출력.build(); }}
이제 추상 클래스를 상속받아 Controller 테스트를 수행할 수 있다.
/** * 테스트시 필요한 공통 설정 추상 클래스(AbstractController) 상속 * PaymentGatewayController 테스트 클래스 * TestMethodOrder : OrderAnnotaion기준으로 테스트 메소드 수행 */@TestMethodOrder(OrderAnnotation.class)publicclassPaymentGatewayControllerTestextendsAbstractControllerTest {privatestaticString[] pmtCodeArr = {"P0001","P0001","P0002","P0003","P0003","P0004","P0005"}; // pmtCode 테스트 데이터 배열privatestaticString[] mbrIdArr = {"0000000345","0000000911","0000000602"}; // mbrId 테스트 데이터 배열 @AutowiredPaymentGatewayController paymentGatewayController; /** * @return 테스트할 paymentController 인스턴스 */ @OverrideprotectedObjectcontroller() {// TODO Auto-generated method stubreturn paymentGatewayController; } /** * Test method for {@linkcom.example.test.controller.PaymentGatewayController#approve(java.lang.String, java.lang.String, java.lang.String, long)}. * 결제 승인 요청 테스트 * 각각 mbrId별로 pmtCodeArr 데이터로 생성 * 이떄, pmtType은 null로 보낸다.(자동으로 생성되도록) * Order annotation은 테스트 실행순서 지정 */ @Test @Order(1)voidtestApprove() {try {for(int i=0;i<7;i++) {for(String mbrId : mbrIdArr) {MultiValueMap<String,String> params =newLinkedMultiValueMap<>();params.add("mbrId", mbrId);params.add("pmtCode", pmtCodeArr[i]);params.add("pmtType","");params.add("pmtAmt","157400");// curl -X POST "http://localhost:8080/api/pg/approve?mbrId=&pmtAmt=&pmtCode=" -H "accept: */*"mockMvc.perform(post("/api/pg/approve").contentType(MediaType.APPLICATION_JSON).params(params)).andExpect(status().isOk()); // 성공여부 확인 } } } catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace(); } } /** * Test method for {@linkcom.example.test.controller.PaymentGatewayController#getRecentPaymentList(java.lang.String, java.lang.String, java.lang.Integer)}. * 최근 결제내역리스트 조회 테스트 * 각각 member 별로 10개씩 조회 */ @Test @Order(2)voidtestGetRecentPaymentList() {try {for(String mbrId : mbrIdArr) {MultiValueMap<String,String> params =newLinkedMultiValueMap<>();params.add("mbrId", mbrId);params.add("size","10");// curl -X GET "http://localhost:8080/api/pg/approve?mbrId=&pmtAmt=&pmtCode=" -H "accept: */*"mockMvc.perform(get("/api/pg/getRecentPaymentList").params(params)).andExpect(status().isOk()) // 수행결과 확인.andExpect(jsonPath("$[*]", hasSize(10))) // 각 멤버별 리스트 수 확인 .andExpect(jsonPath("$[?(@.succYn =='Y')]", hasSize(9))) // 성공여부 성공(Y) 9개 확인.andExpect(jsonPath("$[?(@.succYn =='N')]", hasSize(1))); // 성공여부 실패(N) 1개 확인 } } catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace(); } }}
@Test : 테스트 대상을 지정
@Order : 테스트 수행 순서 지정
@TestMethodOrder(OrderAnnotation.class) : OrderAnnotation 기준으로 테스트 수행