
2022.06.11 - [Back-End/Java] - [JAVA] Socket with JDK
[JAVA] Socket with JDK
2022.04.16 - [실전 공부] - [JAVA x Apache POI] 전략 패턴과 리플렉션을 활용하여 컬럼 자동 생성 엑셀 다운로드 구현하기 [JAVA x Apache POI] 전략 패턴과 리플렉션을 활용하여 컬럼 자동 생성 엑셀 다운로
ktae23.tistory.com
Rest Assured
Rest Assured
의 기본 문법은 BDD
(Behavior Driven Development)와 매우 유사하다.
Given() .param("x", "y") .header("z", "w") .when() .Method() .Then() .statusCode(XXX) .body("x, ”y", equalTo("z"));
Code | Explanation |
---|---|
Given() | 요청의 기반 설정을 할 수 있습니다. 요청 헤더, 쿼리 스트링 또는 경로 변수, 리퀘스트 바디 (컨텐트), 쿠키 등을 전달합니다. 이러한 항목이 필요하지 않을 경우 사용하지 않는 선택 사항입니다. |
When() | 요청을 처리하는 시나리오 전체를 나타냅니다. |
Method() | CRUD 작업을 수행하는 HTTP 메서드를 Method 자리에 사용합니다.(get/post/put/delete) |
Then() | 단언문이나 일치 조건을 선언합니다. |
학습 테스트를 통해 Rest Assured 사용법을 코드로 확인해보자.
maven dependency
java 9 미만 버전
<dependency> <groupId>io.rest-assured</groupId> <artifactId>json-path</artifactId> <version>4.2.0</version> <scope>test</scope> </dependency> <dependency> <groupId>io.rest-assured</groupId> <artifactId>xml-path</artifactId> <version>4.2.0</version> <scope>test</scope> </dependency> <dependency> <groupId>io.rest-assured</groupId> <artifactId>json-schema-validator</artifactId> <version>4.2.0</version> <scope>test</scope> </dependency>
java 9이상 버전
<dependency> <groupId>io.rest-assured</groupId> <artifactId>rest-assured-all</artifactId> <version>4.2.0</version> <scope>test</scope> </dependency>
학습 테스트
테스트 코드를 먼저 작성해보자.
Rest Assured는 주로 시나리오 기반 통합 테스트 목적으로 사용되기 때문에 테스트 메서드를 모아 둔 클래스를 따로 작성하고 이를 호출하여 결과를 검증하는 테스트를 한 곳에서 수행하는 편이다.
TestMain
private static final long MEMBER_ID = 1L; private static final String SUCCESS_MESSAGE = "test String " + MEMBER_ID; @Test @DisplayName("get method test") void getMethodTest() { final ExtractableResponse<Response> testString = getTestString(MEMBER_ID); Assertions.assertEquals(SUCCESS_MESSAGE , testString.response().body().asString()); Assertions.assertEquals(HttpStatus.OK.value(), testString.response().statusCode()); }
TestControllerClass
class TestControllerClass { static ExtractableResponse<Response> getTestString(Long memberId) { return given() .log().all() .contentType(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", memberId) .pathParam("memberId", memberId) .when() .get("/test/{memberId}") .then() .log().all() .extract(); } }
테스트 코드를 작성한 뒤 이를 받아줄 컨트롤러를 작성한다.
TestController
@RequestMapping("/test") @RestController public class TestController { @GetMapping("/{memberId}") public ResponseEntity<String> getTestString(@PathVariable Long memberId) { return ResponseEntity.ok("test String " + memberId); } }
그리고 바로 실행해보겠습니다. 결과는 아래와 같다.
java.net.ConnectException: Connection refused: connect
찾아보니 RestAssured
의 포트를 랜덤 포트
로 설정하여 넣어주는 설정이 필요하다고 한다.
아래와 같이 추가해보자.
TestMain
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class TestMain { @LocalServerPort private int port; @BeforeEach void setUp() { RestAssured.port = port; } //... }
변경 뒤 실행하니 초록불이 들어오며 첫 테스트에 성공한다.
Request method: GET Request URI: http://localhost:65358/test/1 Proxy: <none> Request params: <none> Query params: <none> Form params: <none> Path params: memberId=1 Headers: Authorization=1 Accept=*/* Content-Type=application/json; charset=UTF-8 Cookies: <none> Multiparts: <none> Body: <none> 2022-07-09 17:16:44.745 INFO 18468 --- [o-auto-1-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet' 2022-07-09 17:16:44.745 INFO 18468 --- [o-auto-1-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet' 2022-07-09 17:16:44.746 INFO 18468 --- [o-auto-1-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 0 ms HTTP/1.1 200 Content-Type: text/plain;charset=UTF-8 Content-Length: 13 Date: Sat, 09 Jul 2022 08:16:44 GMT Keep-Alive: timeout=60 Connection: keep-alive test String 1
Rest Assured
는 MockMvc
와 달리 대부분 End to End
테스트에 사용하며 @SpringBootTest
로 실제 요청을 보내서 전체적일 로직을 테스트하는데 사용한다.
때문에 순수한Contoller 레이어
만 분리하여 테스트하는 MockMvc
와 달리 서비스를 비롯한 연관 된 스프링 빈
들에 대한 테스트가 함께 수행 된다.
만일 Rest Assured
를 이용하면서 MockMvc
로 테스트 하듯이 레이어 테스트를 원한다면 별도의 설정을 통해 Controller Layer
만 분리하여 단위 테스트를 할 수 있다.
Rest Assured로 MockMvc 테스트 하기
먼저 MockMvc 사용을 위한 의존성을 추가.
<dependency> <groupId>io.rest-assured</groupId> <artifactId>spring-mock-mvc</artifactId> <version>4.2.0</version> <scope>test</scope> </dependency>
`RestAssuredMockMvc`를 사용하여 테스트를 할때는 어떤 방식으로 실행 할 것인지 선택해야 한다. `Stand Alone`과 `Web Application Context` 중 선택할 수 있고, 실행 방식을 테스트 당 한번으로 사용할 수 있다.
Standalone 방식
한 개 이상의 @Controller
또는 @ControllerAdvice
어노테이션이 있는 클래스를 이용해 RestAssuredMockMvc
를 초기화 한다.
import static io.restassured.module.mockmvc.RestAssuredMockMvc.given; public static ExtractableResponse<Response> getTestString(Long memberId) { return given().standaloneSetup(new TestController()) // 또는 TestMain에서 @Autowired 후 파라미터로 전달 받아 사용
또는 여러 테스트를 수행하는 경우에는 미리 설정을 해둘 수 있다.
@BeforeEach void portSetUp() { RestAssured.port = port; RestAssuredMockMvc.standaloneSetup(new TestController()); }
standaloneSetup()의 시그니처는 아래와 같다. 가변 인자를 사용하기 때문에 하나 이상의 대상을 사용할 수 있다.
public static void standaloneSetup(Object... controllersOrMockMvcConfigurers) { mockMvcFactory = StandaloneMockMvcFactory.of(controllersOrMockMvcConfigurers); }
Web Application Context 방식
spring 의 WebApplicationContext을 이용하는 방식. standaloneSetup()과 유사한 방식으로 테스트 마다 초기화 할 수 있다.
TestMain에서 WebApplicationContext를 주입 받아 인자로 넘겨 준다.
// TestMain @Autowired private WebApplicationContext webApplicationContext; // =================================== // TestContreollerClass public static ExtractableResponse<MockMvcResponse> getTestString(Long memberId, WebApplicationContext context) { return given().webAppContextSetup(context) .log().all() .contentType(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", memberId) .when() .get("/test/{memberId}", memberId) .then() .log().all() .extract(); }
또는 여러 테스트를 위해 미리 설정해둘 수 있다.
@BeforeEach void portSetUp() { RestAssured.port = port; RestAssuredMockMvc.webAppContextSetup(webApplicationContext); }
Rest Assured
를 사용해 Controller Unit Test
를 하기 위한 Mock
설정 방법을 배웠다.
이제 아래와 같이 standaloneSetup()
과 mock을 주입한다.
tandaloneSetup
방식은 사용자가 제공한 mock
만을 이용해서 초기화하기 때문에 유닛 테스트 목적으로 적합
하다.
그리고 이외에 테스트에 필요한 여러 클래스를 추가한다.
BadRequestException (for test)
package com.example.restassuredstudy; public class BadRequestException extends RuntimeException { public BadRequestException() { super(); } }
TestControllerExceptionHandler
package com.example.restassuredstudy; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; @RestControllerAdvice(assignableTypes = TestController.class) public class TestControllerExceptionHandler extends ResponseEntityExceptionHandler { @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(BadRequestException.class) private void badRequest(BadRequestException badRequestException){ }}
TestService
package com.example.restassuredstudy; import org.springframework.stereotype.Service; @Service public class TestService { public String getTestString(Long memberId) { if (1L == memberId) { return "test String " + memberId; } throw new BadRequestException(); } }
그리고 실패 테스트 케이스와 다른 방식의 테스트 코드를 추가한다.
처음에 Rest Assured가 시나리오 기반의 통합/인수 테스트를 위해 사용되는 방식에 대해 설명했는데
이제는 컨트롤러 유닛 테스트를 진행하기 때문에 테스트 검증을 바로 수행하는 케이스를 추가해본다.
TestMain
package com.example.restassuredstudy; import io.restassured.module.mockmvc.RestAssuredMockMvc; import io.restassured.module.mockmvc.response.MockMvcResponse; import io.restassured.response.ExtractableResponse; import joptsimple.internal.Strings; import org.hamcrest.Matchers; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import static com.example.restassuredstudy.TestControllerClass.getTestString; import static io.restassured.module.mockmvc.RestAssuredMockMvc.given; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.core.Is.is; @ExtendWith(MockitoExtension.class) class TestMain { @Mock private TestService testService; @InjectMocks private TestController testController; @InjectMocks private TestControllerExceptionHandler testControllerExceptionHandler; private static final long MEMBER_ID = 1L; private static final long FAIL_MEMBER_ID = 2L; private static final String SUCCESS_MESSAGE = "test String " + MEMBER_ID; @BeforeEach void setUp() { RestAssuredMockMvc.standaloneSetup(testController, testControllerExceptionHandler); } @Test @DisplayName("get method test") void getMethodTest() { Mockito.when(testService.getTestString(MEMBER_ID)).thenReturn(SUCCESS_MESSAGE); final ExtractableResponse<MockMvcResponse> testString = getTestString(MEMBER_ID); Assertions.assertEquals(SUCCESS_MESSAGE, testString.response().body().asString()); Assertions.assertEquals(HttpStatus.OK.value(), testString.response().statusCode()); } @Test @DisplayName("get method test another version") void getMethodTestAnotherVersion() { Mockito.when(testService.getTestString(MEMBER_ID)).thenReturn(SUCCESS_MESSAGE); given() .log().all() .contentType(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", MEMBER_ID) .when() .get("/test/{memberId}", MEMBER_ID) .then() .log().all() .assertThat() .statusCode(HttpStatus.OK.value()) .body(is(equalTo(SUCCESS_MESSAGE))); } @Test @DisplayName("get method fail test") void getMethodFailTest() { Mockito.when(testService.getTestString(FAIL_MEMBER_ID)).thenThrow(new BadRequestException()); final ExtractableResponse<MockMvcResponse> testString = getTestString(FAIL_MEMBER_ID); Assertions.assertTrue(Strings.EMPTY.equals(testString.response().body().asString())); Assertions.assertEquals(HttpStatus.BAD_REQUEST.value(), testString.response().statusCode()); } @Test @DisplayName("get method fail test another version") void getMethodFailTestAnotherVersion() { Mockito.when(testService.getTestString(FAIL_MEMBER_ID)).thenThrow(new BadRequestException()); given() .log().all() .contentType(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", FAIL_MEMBER_ID) .when() .get("/test/{memberId}", FAIL_MEMBER_ID) .then() .log().ifValidationFails() .assertThat() .statusCode(HttpStatus.BAD_REQUEST.value()) .body(Matchers.emptyString()); } }
TestControllerClass
package com.example.restassuredstudy; import io.restassured.module.mockmvc.response.MockMvcResponse; import io.restassured.response.ExtractableResponse; import org.springframework.http.MediaType; import static io.restassured.module.mockmvc.RestAssuredMockMvc.given; class TestControllerClass { public static ExtractableResponse<MockMvcResponse> getTestString(Long memberId) { return given() .log().all() .contentType(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", memberId) .when() .get("/test/{memberId}", memberId) .then() .log().all() .extract(); } }
이렇게 준비한 테스트를 수행하면 결과는 초록불이 뜨며 아래와 같이 로그가 출력 된다.
이러한 방법으로 Mock
과 Rest Assured
를 이용하여 Controller Unit Test
를 수행할 수 있다.
테스트 결과
19:34:38.066 [main] DEBUG _org.springframework.web.servlet.HandlerMapping.Mappings - c.e.r.TestController: {GET [/test/{memberId}]}: getTestString(Long) 19:34:38.066 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - 1 mappings in org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping 19:34:38.070 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter - ControllerAdvice beans: 0 @ModelAttribute, 0 @InitBinder, 1 RequestBodyAdvice, 1 ResponseBodyAdvice 19:34:38.073 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver - ControllerAdvice beans: 1 @ExceptionHandler, 1 ResponseBodyAdvice 19:34:38.073 [main] INFO org.springframework.mock.web.MockServletContext - Initializing Spring TestDispatcherServlet '' 19:34:38.073 [main] INFO org.springframework.test.web.servlet.TestDispatcherServlet - Initializing Servlet '' 19:34:38.073 [main] DEBUG org.springframework.test.web.servlet.TestDispatcherServlet - Detected AcceptHeaderLocaleResolver 19:34:38.073 [main] DEBUG org.springframework.test.web.servlet.TestDispatcherServlet - Detected FixedThemeResolver 19:34:38.073 [main] DEBUG org.springframework.test.web.servlet.TestDispatcherServlet - Detected org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator@e7ecd 19:34:38.073 [main] DEBUG org.springframework.test.web.servlet.TestDispatcherServlet - Detected org.springframework.web.servlet.support.SessionFlashMapManager@e3658c 19:34:38.073 [main] DEBUG org.springframework.test.web.servlet.TestDispatcherServlet - enableLoggingRequestDetails='false': request parameters and headers will be masked to prevent unsafe logging of potentially sensitive data 19:34:38.073 [main] INFO org.springframework.test.web.servlet.TestDispatcherServlet - Completed initialization in 0 ms Request method: GET Request URI: http://localhost:8080/test/1 Proxy: <none> Request params: <none> Query params: <none> Form params: <none> Path params: <none> Headers: Content-Type=application/json Authorization=1 Cookies: <none> Multiparts: <none> Body: <none> 19:34:38.076 [main] DEBUG org.springframework.test.web.servlet.TestDispatcherServlet - GET "/test/1", parameters={} 19:34:38.076 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Mapped to com.example.restassuredstudy.TestController#getTestString(Long) 19:34:38.077 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor - Using 'text/plain', given [*/*] and supported [text/plain, */*, application/json, application/*+json] 19:34:38.077 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor - Writing ["test String 1"] 19:34:38.077 [main] DEBUG org.springframework.test.web.servlet.TestDispatcherServlet - Completed 200 OK 200 Content-Type: text/plain;charset=ISO-8859-1 Content-Length: 13 test String 1
'공부 기록' 카테고리의 다른 글
[SPRING SECURITY]우아한 멀티 타입 빌더 (0) | 2022.10.09 |
---|---|
[Network] 액세스 회선 (0) | 2022.07.18 |
[Network] 라우터 (0) | 2022.07.07 |
[Network] 스위칭 허브 (0) | 2022.07.04 |
[Network] LAN 케이블 (0) | 2022.07.03 |
댓글