728x90
과정 수업 시간 중, Controller Test를 진행했습니다.
Repository 테스트와는 달리 새로운 개념, 메서드 등이 대거 등장하면서 모르는 부분들이 많아 정리를 하였습니다.
단위 테스트
- 단위 테스트는 프로젝트에 필요한 모든 기능에 대한 테스트를 각각 진행하는 것을 의미
- 일반적으로 스프링부트에서는
org.springframework.boot:spring-boot-starter-test
디펜던시만으로 의존성을 모두 가질 수 있다.
F.I.R.S.T 원칙
Fast
: 테스트 코드의 실행은 빠르게 진행되어야함Independent
: 독립적인 테스트가 가능해야함Repeatable
: 테스트는 매번 같은 결과를 만들어야 함Self-Validating
: 테스트는 그 자체로 실행하여 결과를 확인할 수 있어야 함Timely
: 단위 테스트는 비즈니스 코드가 완성되기 전에 구성하고 테스트가 가능해야 한다.- 코드가 완성되기 전부터 테스트가 따라와야 한다는 TDD의 원칙을 담고 있음.
@WebMvcTest(Class명.class)
- ()에 작성된 클래스만 실제로 로드하여 테스트를 진행
- 매개변수를 지정해주지 않으면
@Controller
,@RestController
,@RestControllerAdvice
등 컨트롤러와 연관된 Bean이 모두 로드됨 - 스프링의 모든 Bean을 로드하는
@SpringBootTest
대신 컨트롤러 관련 코드만 테스트할 경우 사용
@WebMvcTest(ArticleRestController.class)
class ArticleRestControllerTest {
}
@Autowired about MockBean
- Controller의 API를 테스트하는 용도인 MockMvc객체를 주입받음
- perform() 메소드를 활용하여 컨트롤러의 동작을 확인할 수 있음
.andExpect()
,.andDo()
,.andReturn()
등의 메소드를 같이 활용함.
@WebMvcTest(ArticleRestController.class)
class ArticleRestControllerTest {
@Autowired
MockMvc mockMvc;
}
@MockBean
- Spring은 객체 관리를 직접하지 않고
Dependency Injection(DI)
를 통해 관리하기 때문에 MockMvc객체 사용하기 쉬움 - 테스트할 클래스에서 주입 받고 있는 객체에 대해 가짜 객체를 생성해주는 어노테이션
- 해당 객체는 실제 행위를 하지 않음
given()
메소드를 활용하여 가짜 객체의 동작에 대해 정의하여 사용할 수 있음.- Controller에서 잡고 있는 Bean 객체에 대해 Mock 형태의 객체를 생성해준다.
@WebMvcTest(ArticleRestController.class)
class ArticleRestControllerTest {
@MockBean
ArticleService articleService;
}
given
- given : Mock 객체가 특정 상황에서 해야하는 행위를 정의하는 메소드
Mockito
라이브러리에서 사용.
given(articleService.getArticleInfo(1L))
.willReturn(articleResponse);
// 행위 정의(아티클서비스.아티클 정보를 불러온다(1L))
// .정보를 불러오면(아티클 리스폰스)를 반환해야한다.
MockMVC
perform()
: RestAPI를 테스트할 수 있는 환경 제공get()
:MockHttpServletRequestBuilder
에 있는get()
메소드.- 실제 어떤 통신을 할 것인지 정의하는 메소드
andExpect()
: 기대하는 값이 나왔는지 체크해볼 수 있는 메소드 (기대 값을 수행)- builder 구조를 가지고 있음.
jsonPath()
: http request를 날리면 json형태의 body값을 받기 때문에 받은 값의 존재 확인- $를 사용하여 json의 key값을 조회 가능
- json path의 depth가 깊어지면 .을 추가하여 추가 탐색이 가능
- (ex : $.hospitalId.hospitalIdName)
- jsonPath git hub repository
- https://github.com/json-path/JsonPath
andDo()
: 요청에 대한 저리verify()
: 해당 객체의 메소드가 실행되었는지 체크
mockMvc.perform(get(url))
.andExpect(status().isOk())
.andExpect(jsonPath("$.title").exists())
.andExpect(jsonPath("$.content").exists())
.andExpect(jsonPath("$.content").value("hi"))
.andDo(print());
verify(articleService).getArticleInfo(articleId);
/*
mockMvc.RestAPI TEST 환경제공(get으로 통신 정의(url))
.기대값 체크(status().응답 상태 코드가 200인가?(정상 처리)()) // 빌더 구조
.기대값 체크(json 확인(title).다음과 같은 키가 존재하는지())
.기대값 체크(json 확인(Content).다음과 같은 키가 존재하는지())
.기대값 체크(json 확인(Content).다음과 같은 키의 값이 다음과 일치하는지("hi"))
.실행 결과 처리(출력());
메소드 확인(아티클 서비스).아티클 정보를 받아오는 메서드(아티클 ID);
*/
objectMapper
ObjectMapper
클래스의writeValueAsString
및writeValueAsBytes
메서드는 Java 개체에서 Json을 생성하고, 생성된 Json을 문자열 또는 바이트 배열로 반환
ObjectMapper objectMapper = new ObjectMapper();
Car car = new Car("yellow", "renault");
String carAsString = objectMapper.writeValueAsString(car);
// {"color":"yellow","type":"renault"}
Gson을 사용한 json 문자열 반환, ObjectMapper를 사용한 json 문자열 반환
Gson gson = new Gson();
String content = gson.toJson(ArticleDto)
// 아래 코드로 json 형태 변경 작업을 대체할 수 있음.
String json = new ObjectMapper().writeValueAsString(ArticleDto);
예시
void add() throws Exception {
ArticleAddRequest dto = new ArticleAddRequest("hi", "welcome");
given(articleService.addArticle(any())).willReturn(new ArticleAddResponse(2L, dto.getTitle(), dto.getContent()));
//objectMapper
String url = "/api/v1/articles/add";
mockMvc.perform(post(url)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsBytes(new ArticleAddRequest("hi", "welcome")))
).andExpect(status().isOk())
.andExpect(jsonPath("$.id").exists())
.andExpect(jsonPath("$.title").exists())
.andExpect(jsonPath("$.title").value("hi"))
.andExpect(jsonPath("$.content").exists())
.andDo(print());
}
/*
행위 정의(아티클서비스.아티클 추가(any()))
.정보를 불러오면(새로운 아티클 추가 리스폰스 객체 생성)를 반환해야한다.
mockMvc.RestAPI TEST 환경제공(통신 정의(url))
.어떠한 컨텐트 타입을 지정할 것인가(JSON형태 사용)
.어떠한 body값을 넘겨줄 것인지(오브젝트매퍼.Java 개체에서 Json을 생성하고, 바이트 배열로 반환(아티스트 추가 요청 객체))
.기대값 체크(status().응답 상태 코드가 200인가?(정상 처리)()) // 빌더 구조
.기대값 체크(json 확인(it).다음과 같은 키가 존재하는지())
.기대값 체크(json 확인(title).다음과 같은 키가 존재하는지())
.기대값 체크(json 확인(title).다음과 같은 키의 값이 다음과 일치하는지("hi"))
.기대값 체크(json 확인(Content).다음과 같은 키가 존재하는지())
.실행 결과 처리(출력());
*/
Object Mapper 소개
https://www.baeldung.com/jackson-object-mapper-tutorial
new 객체 생성하여 given에 대입했을 때 테스트 fail이 발생한 이유
- DTO에
@Data
어노테이션이 부재하였기 때문- •
@Getter
,@Setter
,@RequiredArgsConstructor
,@ToString
,@EqualsAndHashCode
를 한번에 설정해주는 어노테이션이다.
- •
@EqualsAndHashCode
란?equals
,hashCode
를 자동 생성한다.equals
: 두 객체의 내용이 같은지, 동등성(equality) 를 비교하는 연산자hashCode
: 두 객체가 같은 객체인지, 동일성(identity) 를 비교하는 연산자
any()
사용 했을 때 테스트가 통과한 이유
- 모든 매개 변수에 대하여 같은 행동을 하는 Mock 객체를 만들 수 있음.
- Matches anything, including nulls and varargs.
- We are stubbing bool() method to return “true” for any string, integer and object arguments. All the below assertions will pass in this case:
참조
테스트 코드 (Junit, TDD)
@Data, @EqualsAndHashCode
Mockito ArgumentMatchers
https://www.baeldung.com/mockito-argument-matchers
Jackson ObjectMapper 정리
https://www.baeldung.com/jackson-object-mapper-tutorial
반응형
'Server > Spring&Spring Boot' 카테고리의 다른 글
[Docs] Swagger 도입 (0) | 2022.11.24 |
---|---|
[Spring Security] 기본 유저, 비밀번호 변경 (0) | 2022.11.19 |
[토비의 스프링] 싱글톤 레지스트리와 오브젝트 스코프 (0) | 2022.11.12 |
[스스로 이해해보는 스프링] 책임의 분리 (0) | 2022.10.29 |
[토비의 스프링] 스프링의 IoC - 애플리케이션 컨텍스트의 동작 방식 (0) | 2022.10.29 |