728x90
Mock 이란?
모조품이란 뜻으로, 테스트 시 당장 구현하기 어려운 객체를 대신해 가짜로 생성해놓은 객체이다.
Mock 테스트 코드
@AutoConfigureMockMvc
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
public class ProductRestControllerTest {
@Autowired
private MockMvc mvc;
@Test
// 전체 상품 목록 조회
public void findAll_test() throws Exception {
// when
ResultActions resultActions = mvc.perform(
get("/products")
);
String responseBody = resultActions.andReturn().getResponse().getContentAsString();
System.out.println("테스트 : " + responseBody);
// verify
resultActions.andExpect(jsonPath("$.success").value("true"));
resultActions.andExpect(jsonPath("$.response[0].id").value(1));
resultActions.andExpect(jsonPath("$.response[0].productName").value("기본에 슬라이딩 지퍼백 크리스마스/플라워에디션 에디션 외 주방용품 특가전"));
resultActions.andExpect(jsonPath("$.response[0].description").value(""));
resultActions.andExpect(jsonPath("$.response[0].image").value("/images/1.jpg"));
resultActions.andExpect(jsonPath("$.response[0].price").value(1000));
resultActions.andExpect(jsonPath("$.response[1].id").value(2));
resultActions.andExpect(jsonPath("$.response[1].productName").value("[황금약단밤 골드]2022년산 햇밤 칼집밤700g외/군밤용/생율"));
resultActions.andExpect(jsonPath("$.response[1].description").value(""));
resultActions.andExpect(jsonPath("$.response[1].image").value("/images/2.jpg"));
resultActions.andExpect(jsonPath("$.response[1].price").value(2000));
}
응답이 mock과 일치하는지 테스트한다.
@AutoConfigureMockMvc
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@ImportAutoConfiguration
@PropertyMapping("spring.test.mockmvc")
public @interface AutoConfigureMockMvc {
boolean addFilters() default true;
...
}
@SpringBootTest에서 Mock 테스트시 필요한 의존성을 주입해준다.
MockMvc
public final class MockMvc {
static final String MVC_RESULT_ATTRIBUTE = MockMvc.class.getName().concat(".MVC_RESULT_ATTRIBUTE");
private final TestDispatcherServlet servlet;
private final Filter[] filters;
private final ServletContext servletContext;
@Nullable
private RequestBuilder defaultRequestBuilder;
@Nullable
private Charset defaultResponseCharacterEncoding;
...
//생성자
MockMvc(TestDispatcherServlet servlet, Filter... filters) {
Assert.notNull(servlet, "DispatcherServlet is required");
Assert.notNull(filters, "Filters cannot be null");
Assert.noNullElements(filters, "Filters cannot contain null values");
this.servlet = servlet;
this.filters = filters;
this.servletContext = servlet.getServletContext();
}
...
}
메서드
- perform(RequestBuilder builder)
- RequestBuilder에 요청 정보(http 메서드, url, 쿠키 등등)를 담아 해당 요청을 보낸다.
- 결과로 ResultActions을 반환하며, andExpect()로 검증할 수 있다.
mockMvc.perform(get("/form")
.param("username", "Sam")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(info))
);
- andExpect(ResultMatcher matcher)
- 실행 결과를 검증한다.
- ResultMatcher로 상태코드, 헤더, 쿠키, content등을 검증할 수 있다.
mockMvc.perform(get("/person/1"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.person.name").value("Jason"));
- andExpectAll(ResultMatcher... matchers)
- 한번에 검증 내용을 작성할 수 있다.
mockMvc.perform(get("/person/1"))
.andExpectAll(
status().isOk(),
content().contentType(MediaType.APPLICATION_JSON),
jsonPath("$.person.name").value("Jason")
);
- andDo(ResultHandler handler)
- handler에 따라 결과를 출력한다.
- ResultHandler에는 log() : 로그 출력, print(): System.out 출력 이 있다.
mockMvc.perform(get("/form")).andDo(print());
사용 방법
WebApplicationContext wac = ...;
MockMvc mockMvc = webAppContextSetup(wac).build();
mockMvc.perform(get("/form")) //GET 요청
.andExpectAll( //검증 내용
status().isOk(), //상태코드
content().contentType("text/html"), //컨텐츠 타입
forwardedUrl("/WEB-INF/layouts/main.jsp") //이동 대상 경로 검증
)
.andDo(print()); //결과 출력
JsonPath
JSON 형식의 데이터를 쉽게 처리할 수 있는 표현식
표현 방법
dot으로 표현하거나 bracket을 이용해 표현할 수 있다.
- dot : x.store.book[0].title
- bracket : x['store']['book'][0]['title']
문법 요소
$ | 루트 개체 또는 배열입니다. |
.property | 상위 개체에서 지정한 속성을 선택합니다. |
['property'] | 상위 개체에서 지정한 속성을 선택합니다.속성 이름 뒤에 작은 따옴표를 붙여야 합니다. 팁:속성 이름에 공백과 같은 특수 문자가 포함되어 있거나 다음 문자로 시작하는 경우 이 표기법을 사용합니다.A..Za..z_. |
[n] | 배열에서 n번째 요소를 선택합니다.인덱스는 0을 기반으로 합니다. |
[index1,index2,…] | 지정된 인덱스가 있는 배열 요소를 선택합니다.list을 반환합니다. |
..property | 재귀 하강: 지정한 속성 이름을 재귀적으로 검색하고 이 속성 이름을 가진 모든 값의 배열을 반환합니다.속성이 하나만 발견된 경우에도 항상 list을 반환합니다. |
* | 와일드카드는 이름이나 인덱스에 관계없이 개체 또는 배열의 모든 요소를 선택합니다.예를들면,address.*의 모든 속성을 의미합니다. |
[start:end] [start:] |
시작 인덱스와 끝 인덱스를 포함하지 않고 최대로 배열 요소를 선택합니다.끝을 생략하면 배열의 시작부터 끝까지 모든 요소를 선택합니다.list을 반환합니다. |
[:n] | 배열의 첫 번째 n개 요소를 선택합니다.list을 반환합니다. |
[-n:] | 배열의 마지막 n개 요소를 선택합니다.list을 반환합니다. |
[?(expression)] | filter식.지정한 필터와 일치하는 개체 또는 배열의 모든 요소를 선택합니다.list을 반환합니다. |
[(expression)] | 명시적 속성 이름 또는 인덱스 대신 스크립트 식을 사용할 수 있습니다.예를 들면[(@.length-1)]배열의 마지막 항목을 선택합니다.여기서,length이름이 지정된 JSON 필드가 아닌 현재 배열의 길이를 나타냅니다. |
@ | 처리 중인 현재 노드를 나타내는 필터 식에 사용됩니다. |
(해석해서 가져온거라 문장이 어색하다.)
연산자 외 필터 등 자세한 문법은 아래 링크에서 볼 수 있다.
https://support.smartbear.com/alertsite/docs/monitors/api/endpoint/jsonpath.html
Mock 테스트 코드 일부
String responseBody = resultActions.andReturn().getResponse().getContentAsString();
System.out.println("테스트 : " + responseBody);
응답 결과를 문자열로 출력하는 코드이다.
resultActions.andExpect(jsonPath("$.success").value("true"));
json 데이터에서 success의 값을 찾아 true와 비교하는 코드이다.
jsonPath가 반환한 JsonPathResultMatchers에서 제공하는 value 메서드를 이용해 테스트 값을 비교했다.
🌿 값을 비교하는 value 메서드
public class JsonPathResultMatchers {
private final JsonPathExpectationsHelper jsonPathHelper;
@Nullable
private String prefix;
public ResultMatcher value(@Nullable Object expectedValue) {
return result -> this.jsonPathHelper.assertValue(getContent(result), expectedValue);
}
...
}
실제 값인 result와 예상값인 expectedValue가 일치하는지 비교한다.
Dummy 데이터 Mock Test
DB 없이 가짜 데이터를 집어넣고 테스트 하는 코드이다.
Dummy Data
FakeStore.java
@Getter
@Component
public class FakeStore {
private List<Product> productList = productDummyList();
private List<Option> optionList = optionDummyList(productList);
private Product newProduct(Integer id, String productName, int imageNumber, int price) {
return Product.builder()
.id(id)
.productName(productName)
.description("")
.image("/images/" + imageNumber + ".jpg")
.price(price)
.build();
}
private Option newOption(Product product, Integer id, String optionName, int price) {
return Option.builder()
.product(product)
.id(id)
.optionName(optionName)
.price(price)
.build();
}
//Dummy Data
private List<Product> productDummyList(){
return Arrays.asList(
newProduct(1, "기본에 슬라이딩 지퍼백 크리스마스/플라워에디션 에디션 외 주방용품 특가전", 1, 1000),
newProduct(2, "[황금약단밤 골드]2022년산 햇밤 칼집밤700g외/군밤용/생율", 2, 2000),
newProduct(3, "삼성전자 JBL JR310 외 어린이용/성인용 헤드셋 3종!", 3, 30000),
newProduct(4, "바른 누룽지맛 발효효소 2박스 역가수치보장 / 외 7종", 4, 4000),
newProduct(5, "[더주] 컷팅말랑장족, 숏다리 100g/300g 외 주전부리 모음 /중독성 최고/마른안주", 5, 5000),
newProduct(6, "굳지않는 앙금절편 1,050g 2팩 외 우리쌀떡 모음전", 6, 15900),
newProduct(7, "eoe 이너딜리티 30포, 오렌지맛 고 식이섬유 보충제", 7, 26800),
newProduct(8, "제나벨 PDRN 크림 2개. 피부보습/진정 케어", 8, 25900),
newProduct(9, "플레이스테이션 VR2 호라이즌 번들. 생생한 몰입감", 9, 797000),
newProduct(10, "통영 홍 가리비 2kg, 2세트 구매시 1kg 추가증정", 10, 8900),
newProduct(11, "아삭한 궁채 장아찌 1kg 외 인기 반찬 모음전", 11, 6900),
newProduct(12, "깨끗한나라 순수소프트 30롤 2팩. 무형광, 도톰 3겹", 12, 28900),
newProduct(13, "생활공작소 초미세모 칫솔 12입 2개+가글 증정", 13, 9900),
newProduct(14, "경북 영천 샤인머스켓 가정용 1kg 2수 내외", 14, 9900),
newProduct(15, "[LIVE][5%쿠폰] 홈카페 Y3.3 캡슐머신 베이직 세트", 15, 148000)
);
}
private List<Option> optionDummyList(List<Product> productList) {
return Arrays.asList(
newOption(productList.get(0), 1,"01. 슬라이딩 지퍼백 크리스마스에디션 4종", 10000),
newOption(productList.get(0), 2,"02. 슬라이딩 지퍼백 플라워에디션 5종", 10900),
newOption(productList.get(0), 3,"고무장갑 베이지 S(소형) 6팩", 9900),
newOption(productList.get(0), 4,"뽑아쓰는 키친타올 130매 12팩", 16900),
newOption(productList.get(0), 5,"2겹 식빵수세미 6매", 8900),
newOption(productList.get(1), 6,"22년산 햇단밤 700g(한정판매)", 9900),
newOption(productList.get(1), 7,"22년산 햇단밤 1kg(한정판매)", 14500),
newOption(productList.get(1), 8,"밤깎기+다회용 구이판 세트", 5500),
newOption(productList.get(2), 9,"JR310 (유선 전용) - 블루", 29900),
newOption(productList.get(2), 10,"JR310BT (무선 전용) - 레드", 49900),
newOption(productList.get(2), 11,"JR310BT (무선 전용) - 그린", 49900),
newOption(productList.get(2), 12,"JR310BT (무선 전용) - 블루", 49900),
newOption(productList.get(2), 13,"T510BT (무선 전용) - 블랙", 52900),
newOption(productList.get(2), 14,"T510BT (무선 전용) - 화이트", 52900),
newOption(productList.get(3), 15,"선택01_바른곡물효소 누룽지맛 2박스", 17900), //15
newOption(productList.get(3), 16,"선택02_바른곡물효소누룽지맛 6박스", 50000),
newOption(productList.get(3), 17,"선택03_바른곡물효소3박스+유산균효소3박스", 50000),
newOption(productList.get(3), 18,"선택04_바른곡물효소3박스+19종유산균3박스", 50000),
newOption(productList.get(4), 19,"01. 말랑컷팅장족 100g", 4900),
newOption(productList.get(4), 20,"02. 말랑컷팅장족 300g", 12800),
newOption(productList.get(4), 21,"03. 눌린장족 100g", 4900),
newOption(productList.get(5), 22,"굳지않는 쑥 앙금 절편 1050g", 15900),
newOption(productList.get(5), 23,"굳지않는 흑미 앙금 절편 1050g", 15900),
newOption(productList.get(5), 24,"굳지않는 흰 가래떡 1050g", 15900),
newOption(productList.get(6), 25,"이너딜리티 1박스", 26800), //25
newOption(productList.get(6), 26,"이너딜리티 2박스+사은품 2종", 49800),
newOption(productList.get(7), 27,"제나벨 PDRN 자생크림 1+1", 25900),
newOption(productList.get(8), 28,"플레이스테이션 VR2 호라이즌 번들", 839000),
newOption(productList.get(8), 29,"플레이스테이션 VR2", 797000),
newOption(productList.get(9),30, "홍가리비2kg(50미이내)", 8900), //30
newOption(productList.get(10),31, "궁채 절임 1kg", 6900),
newOption(productList.get(10),32, "양념 깻잎 1kg", 8900),
newOption(productList.get(10),33, "된장 깻잎 1kg", 8900),
newOption(productList.get(10),34, "간장 깻잎 1kg", 7900),
newOption(productList.get(10),35, "고추 무침 1kg", 8900),
newOption(productList.get(10),36, "파래 무침 1kg", 9900),
newOption(productList.get(11),37, "01_순수소프트 27m 30롤 2팩", 28900),
newOption(productList.get(11),38, "02_벚꽃 프리미엄 27m 30롤 2팩", 32900),
newOption(productList.get(12),39, "(증정) 초미세모 칫솔 12개 x 2개", 11900),
newOption(productList.get(12),40, "(증정) 잇몸케어 치약 100G 3개 x 2개", 16900),
newOption(productList.get(12),41, "(증정) 구취케어 치약 100G 3개 x 2개", 16900),
newOption(productList.get(12),42, "(증정)화이트케어 치약 100G 3개 x 2개", 19900),
newOption(productList.get(12),43, "(증정) 어린이 칫솔 12EA", 9900),
newOption(productList.get(13),44, "[가정용] 샤인머스켓 1kg 2수내외", 9900),
newOption(productList.get(13),45, "[특품] 샤인머스켓 1kg 1-2수", 12900), //45
newOption(productList.get(13),46, "[특품] 샤인머스켓 2kg 2-3수", 23900),
newOption(productList.get(14),47, "화이트", 148000),
newOption(productList.get(14),48, "블랙", 148000)
);
}
}
Mock Controller - 페이징 , 개별 조회 (id)
ProductRestController
@RequiredArgsConstructor
@RestController
public class ProductRestController {
private final FakeStore fakeStore;
// (기능4) 전체 상품 목록 조회 (페이징 9개씩)
@GetMapping("/products")
public ResponseEntity<?> findAll(@RequestParam(defaultValue = "0") int page) {
// 1. 더미데이터 가져와서 페이징하기
List<Product> productList = fakeStore.getProductList().stream().skip(page*9).limit(9).collect(Collectors.toList());
// 2. DTO 변환
List<ProductResponse.FindAllDTO> responseDTOs =
productList.stream().map(ProductResponse.FindAllDTO::new).collect(Collectors.toList());
// 3. 공통 응답 DTO 만들기
return ResponseEntity.ok(ApiUtils.success(responseDTOs));
}
// (기능5) 개별 상품 상세 조회
@GetMapping("/products/{id}")
public ResponseEntity<?> findById(@PathVariable int id) {
// 1. 더미데이터 가져와서 상품 찾기
Product product = fakeStore.getProductList().stream().filter(p -> p.getId() == id).findFirst().orElse(null);
// 2. 더미데이터 가져와서 해당 상품에 옵션 찾기
List<Option> optionList = fakeStore.getOptionList().stream().filter(option -> product.getId() == option.getProduct().getId()).collect(Collectors.toList());
// 3. DTO 변환
ProductResponse.FindByIdDTO responseDTO = new ProductResponse.FindByIdDTO(product, optionList);
// 4. 공통 응답 DTO 만들기
return ResponseEntity.ok(ApiUtils.success(responseDTO));
}
}
참고
Dummy Data : 카카오 테크 캠퍼스 - 메타 코딩
728x90
'Spring > Spring 개발 상식' 카테고리의 다른 글
Spring Security Test (0) | 2023.07.08 |
---|---|
DB : 기본키, 외래키, 제약조건 (1) | 2023.07.06 |
@Controller, @RestController, @ResponseEntity (0) | 2023.07.05 |
REST API 설계 (0) | 2023.06.26 |
IaaS vs PaaS vs SaaS (0) | 2023.06.26 |