Latest commit

History

History
393 lines (310 loc) · 12.4 KB

File metadata and controls

393 lines (310 loc) · 12.4 KB

Spring Boot Test 정리

1. @SpringBootTest

통합 테스트를 제공

  • 실제 구동되는 어플리케이션과 똑같이 ApplicationContext를 로드하여 테스트
    • 하고 싶은 테스트를 모두 수행 가능
    • 어플리케이션에 설정된 bean을 모두 로드하기 때문에 규모가 클수록 느리다. (단위 테스트가 무의미해짐)
  • 어플리케이션이 실행될 때의 설정을 임의로 바꾸어 테스트 진행 가능
  • 여러 단위 테스트를 하나의 통합된 테스트로 수행할 때 적합
  • 메인클래스와 함께 기본 제공
@RunWith(SpringRunner.class)
@SpringBootTestpublicclassDemoApplicationTests {
@TestpublicvoidcontextLoads() {
}
}

1.1 @RunWith

  • JUnit에 내장된 Runner를 사용하는 대신 어노테이션에 정의된 Runner 클래스 사용
  • @SpringBootTest 를 사용하려면 JUnit 실행에 필요한 SpringJUnit4ClassRunner 클래스를 상속받은 @RunWith(SpringRunner.class)를 붙여야 한다.

1.2. @SpringBootTest 의 파라미터들

value : 테스트가 실행되기 전에 적용할 프로퍼티 주입.(기존의 프로퍼티 오버라이드)
properties : 테스트가 실행되기 전에 {key=value} 형식으로 프로퍼티 추가.
classes : ApplicationContext에 로드할 클래스 지정. (지정하지 않으면 @SpringBootConfiguration을 찾아서 로드)
webEnvironment : 어플리케이션이 실행될 때의 웹 환경을 설정. (기본값은 Mock 서블릿을 로드하여 구동)

// value와 properties는 함께 사용할 수 없으므로 에러 발생@RunWith(SpringRunner.class)
@SpringBootTest(value = "value=test"
, properties = {"property.value=propertyTest"}
, classes = {DemoApplicationTests.class}
, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
publicclassDemoApplicationTests {
@Value("${value}")
privateStringvalue;
@Value("${property.value}")
privateStringpropertyValue;
@TestpublicvoidcontextLoads() {
assertThat(value, is("test"));
assertThat(propertyValue, is("propertyTest"));
}
}

1.3. 사용 팁

  • 프로파일(개발, QA, 운영) 마다 다른 DataSource를 갖는 경우, @ActiveProfiles("local") 을 사용
  • @Transactional을 사용하면 테스트를 마치고 나서 수정된 데이터가 롤백된다.
  • @SpringBootTest는 기본적으로 @SpringBootApplication 이나 @SpringBootConfiguration 을 찾는다. (둘 중 하나는 필수)

2. @WebMvcTest

MVC를 위한 테스트

  • 웹에서 테스트하기 힘든 Controller를 테스트하는데 적합
  • 웹상에서 요청과 응답에 대해 테스트할 수 있다.
  • Security와 Filter까지 자동으로 테스트하며, 수동으로 추가/삭제 가능
  • @WebMvcTest 를 사용하면 MVC 관련 설정들만 로드되기 때문에 가볍게 테스트 가능
    • MVC 관련 설정: @Controller, @ControllerAdvice, @JsonComponent, Filter, WebMvcConfigurer, HandlerMethodArgumentResolver

2.1. 예시

BookController에서 책 리스트를 받아오는 테스트

2.1.1.Book 클래스 생성

@NoArgsConstructor@GetterpublicclassBook {
privateIntegeridx;
privateStringtitle;
privateLocalDateTimepublishedAt;
@BuilderpublicBook(Stringtitle, LocalDateTimepublishedAt) {
this.title = title;
this.publishedAt = publishedAt;
}
}

2.1.2. BookController 클래스 생성

@ControllerpublicclassBookController {
@AutowiredprivateBookServicebookService;
@GetMapping("/books")
publicStringgetBookList(Modelmodel) {
model.addAttribute("bookList", bookService.getBookList());
return"book";
}
}

2.1.3. BookService 인터페이스 생성

이 인터페이스를 구현하는 구현체는 만들지 않고, Mock 데이터를 이용하여 테스트 진행

publicinterfaceBookService {
List<Book> getBookList();
}

2.1.4. BookControllerTest 생성

@RunWith(SpringRunner.class)
@WebMvcTest(BookController.class)
publicclassBookControllerTest {
@AutowiredprivateMockMvcmvc;
@MockBeanprivateBookServicebookService;
@TestpublicvoidBook_MVC_TEST() throwsException {
Bookbook = newBook("Spring Boot Book", LocalDateTime.now());
given(bookService.getBookList()).willReturn(Collections.singletonList(book));
mvc.perform(get("/books"))
.andExpect(status().isOk()) // HTTP 상태값이 200인지 테스트
.andExpect(view().name("book")) // 반환되는 view의 이름이 book인지 테스트
.andExpect(model().attributeExists("bookList")) // Model의 프로퍼티 중 bookList라는 프로퍼티가 존재하는지 테스트
.andExpect(model().attribute("bookList", contains(book))); // Model의 프로퍼티 중 bookList 프로퍼티에 book 객체가 담겨져 있는지 테스트
}
}
  • @WebMvcTest를 사용하기 위해서는 테스트할 컨트롤러 이름을 명시해야 한다. (여기서는 BookContoller)
  • MockMvc는 모든 의존성을 로드하지 않고 BookContoller와 관련된 bean만 로드한다.
    • 여기서는 MockMvc를 주입시켰기 때문에 전체 HTTP서버를 실행하지 않고 테스트 가능
  • BookService를 구현한 구현체는 없지만 @MockBean으로 BookService를 가짜객체로 대체함
  • @DataJpaTest 는 JPA 테스트가 끝날 때 마다 자동으로 사용된 데이터를 롤백
  • EntityManager의 대체재로 만들어진 테스트용 TestEntityManager를 사용하여 persist, flush, find 등의 기본적인 JPA 테스트 가능

3. @DataJpaTest

JPA 관련 테스트 설정만 로드

  • DataSource의 설정이 정상적인지 테스트
  • JPA를 사용하여 데이터를 제대로 생성, 수정, 삭제하는지 테스트
  • 인메모리 임베디드 데이터베이스 사용
    • 메인 메모리를 데이터 저장소로 하여 DB를 어플리케이션에 내장하여 운용하는 DB
  • @Entity 클래스를 스캔하여 Spring Data JPA Repositories를 구성

3.1. 별도의 DataSource 사용하기

기본 설정된 DataSource를 사용하지 않도록 아래와 같이 설정

@RunWith(SpringRunner.class)
@DataJpaTest@ActiveProfiles("...")
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
publicclassJpaTest {
...
}

또는 application.yml 파일을 아래와 같이 수정

spring.test.database.replace: NONE

3.2. 테스트 데이터베이스 선택

3.2.1. 프로퍼티 설정

spring.test.database.connection: H2

3.2.2. 어노테이션 설정

@AutoConfigureTestDatabase(connection = H2)
...

3.3. 예시

3.3.1. Book 클래스에 JPA 관련 어노테이션 추가

@NoArgsConstructor@Getter@Entity@TablepublicclassBook {
@Id@GeneratedValueprivateIntegeridx;
@ColumnprivateStringtitle;
@ColumnprivateLocalDateTimepublishedAt;
@BuilderpublicBook(Stringtitle, LocalDateTimepublishedAt) {
this.title = title;
this.publishedAt = publishedAt;
}
}

3.3.2. BookRepository 생성

publicinterfaceBookRepositoryextendsJpaRepository<Book, Integer> {
}

3.3.3. @DataJpaTest로 테스트 수행하기

@RunWith(SpringRunner.class)
@DataJpaTestpublicclassBookJpaTest {
privatefinalstaticStringBOOT_TEST_TITLE = "Spring Boot Test Book";
@AutowiredprivateTestEntityManagertestEntityManager;
@AutowiredprivateBookRepositorybookRepository;
@TestpublicvoidBookList_Save_Test() {
Bookbook = Book.builder().title(BOOT_TEST_TITLE)
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book);
assertThat(bookRepository.getOne(book.getIdx()), is(book));
}
@TestpublicvoidBookList_Save_Search_Test() {
Bookbook1 = Book.builder().title(BOOT_TEST_TITLE + "1")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book1);
Bookbook2 = Book.builder().title(BOOT_TEST_TITLE + "2")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book2);
Bookbook3 = Book.builder().title(BOOT_TEST_TITLE + "3")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book3);
List<Book> bookList = bookRepository.findAll();
assertThat(bookList, hasSize(3));
assertThat(bookList, contains(book1, book2, book3));
}
@TestpublicvoidBookList_Save_Delete_Test() {
Bookbook1 = Book.builder().title(BOOT_TEST_TITLE + "1")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book1);
Bookbook2 = Book.builder().title(BOOT_TEST_TITLE + "2")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book2);
bookRepository.deleteAll();
assertThat(bookRepository.findAll(), IsEmptyCollection.empty());
}
}

4. @RestClientTest

REST 통신의 데이터형으로 사용되는 JSON 형식이 예상대로 응답을 반환하는지 등을 테스트

4.1. REST 테스트를 위한 BookRestController

@RestControllerpublicclassBookRestController {
@AutowiredprivateBookRestServicebookRestService;
@GetMapping(path = "/rest/test", produces = MediaType.APPLICATION_JSON_VALUE)
publicBookgetRestBooks() {
returnbookRestService.getRestBook();
}
}

getRestBook() 메서드의 반환값은 Book 객체이지만 @RestController로 설정되어 있으면 JSON 형식의 String형으로 반환된다.

4.2. REST 테스트용 BookRestService 생성

@ServicepublicclassBookRestService {
privatefinalRestTemplaterestTemplate;
publicBookRestService(RestTemplateBuilderrestTemplateBuilder) {
this.restTemplate = restTemplateBuilder.rootUri("/rest/test").build();
}
publicBookgetRestBook() {
returnthis.restTemplate.getForObject("/rest/test", Book.class);
}
}

4.3. @RestClientTest 를 사용한 REST 테스트 코드

@RunWith(SpringRunner.class)
@RestClientTest(BookRestService.class)
publicclassBookRestTest {
@RulepublicExpectedExceptionthrown = ExpectedException.none();
@AutowiredprivateBookRestServicebookRestService;
@AutowiredprivateMockRestServiceServerserver;
@Testpublicvoidrest_test() {
this.server.expect(requestTo("/rest/test"))
.andRespond(withSuccess(newClassPathResource("/test.json", getClass())
, MediaType.APPLICATION_JSON));
Bookbook = this.bookRestService.getRestBook();
assertThat(book.getTitle()).isEqualTo("테스트");
}
@Testpublicvoidrest_error_test() {
this.server.expect(requestTo("/rest/test"))
.andRespond(withServerError());
this.thrown.expect(HttpServerErrorException.class);
this.bookRestService.getRestBook();
}
}

4.4. 테스트 코드에 필요한 리소스 파일

{"idx":null,"title":"테스트","publishedAt":null}

5. @JsonTest

JSON의 직렬화(Serialization)와 역직렬화(Deserialization)를 수행하는 라이브러리인 Gson과 Jackson API의 테스트를 제공

JSON 테스트는 두 가지로 나뉜다. 문자열로 나열된 JSON 데이터를 객체로 변환하여 변환된 객체값을 테스트하거나 그 반대.

5.1. 예시

@RunWith(SpringRunner.class)
@JsonTestpublicclassBookJsonTest {
@AutowiredprivateJacksonTester<Book> json;
@Testpublicvoidjson_test() throwsException {
Bookbook = Book.builder()
.title("테스트")
.build();
Stringcontent = "{\"title\":\"테스트\"}";
assertThat(this.json.parseObject(content).getTitle()).isEqualTo(book.getTitle());
assertThat(this.json.parseObject(content).getPublishedAt()).isNull();
assertThat(this.json.write(book)).isEqualToJson("/test.json");
assertThat(this.json.write(book)).hasJsonPathStringValue("title");
assertThat(this.json.write(book)).extractingJsonPathStringValue("title").isEqualTo("테스트");
}
}

*Reference

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Latest commit

History

History
393 lines (310 loc) · 12.4 KB

File metadata and controls

393 lines (310 loc) · 12.4 KB

Spring Boot Test 정리

1. @SpringBootTest

통합 테스트를 제공

  • 실제 구동되는 어플리케이션과 똑같이 ApplicationContext를 로드하여 테스트
    • 하고 싶은 테스트를 모두 수행 가능
    • 어플리케이션에 설정된 bean을 모두 로드하기 때문에 규모가 클수록 느리다. (단위 테스트가 무의미해짐)
  • 어플리케이션이 실행될 때의 설정을 임의로 바꾸어 테스트 진행 가능
  • 여러 단위 테스트를 하나의 통합된 테스트로 수행할 때 적합
  • 메인클래스와 함께 기본 제공
@RunWith(SpringRunner.class)
@SpringBootTestpublicclassDemoApplicationTests {
@TestpublicvoidcontextLoads() {
}
}

1.1 @RunWith

  • JUnit에 내장된 Runner를 사용하는 대신 어노테이션에 정의된 Runner 클래스 사용
  • @SpringBootTest 를 사용하려면 JUnit 실행에 필요한 SpringJUnit4ClassRunner 클래스를 상속받은 @RunWith(SpringRunner.class)를 붙여야 한다.

1.2. @SpringBootTest 의 파라미터들

value : 테스트가 실행되기 전에 적용할 프로퍼티 주입.(기존의 프로퍼티 오버라이드)
properties : 테스트가 실행되기 전에 {key=value} 형식으로 프로퍼티 추가.
classes : ApplicationContext에 로드할 클래스 지정. (지정하지 않으면 @SpringBootConfiguration을 찾아서 로드)
webEnvironment : 어플리케이션이 실행될 때의 웹 환경을 설정. (기본값은 Mock 서블릿을 로드하여 구동)

// value와 properties는 함께 사용할 수 없으므로 에러 발생@RunWith(SpringRunner.class)
@SpringBootTest(value = "value=test"
, properties = {"property.value=propertyTest"}
, classes = {DemoApplicationTests.class}
, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
publicclassDemoApplicationTests {
@Value("${value}")
privateStringvalue;
@Value("${property.value}")
privateStringpropertyValue;
@TestpublicvoidcontextLoads() {
assertThat(value, is("test"));
assertThat(propertyValue, is("propertyTest"));
}
}

1.3. 사용 팁

  • 프로파일(개발, QA, 운영) 마다 다른 DataSource를 갖는 경우, @ActiveProfiles("local") 을 사용
  • @Transactional을 사용하면 테스트를 마치고 나서 수정된 데이터가 롤백된다.
  • @SpringBootTest는 기본적으로 @SpringBootApplication 이나 @SpringBootConfiguration 을 찾는다. (둘 중 하나는 필수)

2. @WebMvcTest

MVC를 위한 테스트

  • 웹에서 테스트하기 힘든 Controller를 테스트하는데 적합
  • 웹상에서 요청과 응답에 대해 테스트할 수 있다.
  • Security와 Filter까지 자동으로 테스트하며, 수동으로 추가/삭제 가능
  • @WebMvcTest 를 사용하면 MVC 관련 설정들만 로드되기 때문에 가볍게 테스트 가능
    • MVC 관련 설정: @Controller, @ControllerAdvice, @JsonComponent, Filter, WebMvcConfigurer, HandlerMethodArgumentResolver

2.1. 예시

BookController에서 책 리스트를 받아오는 테스트

2.1.1.Book 클래스 생성

@NoArgsConstructor@GetterpublicclassBook {
privateIntegeridx;
privateStringtitle;
privateLocalDateTimepublishedAt;
@BuilderpublicBook(Stringtitle, LocalDateTimepublishedAt) {
this.title = title;
this.publishedAt = publishedAt;
}
}

2.1.2. BookController 클래스 생성

@ControllerpublicclassBookController {
@AutowiredprivateBookServicebookService;
@GetMapping("/books")
publicStringgetBookList(Modelmodel) {
model.addAttribute("bookList", bookService.getBookList());
return"book";
}
}

2.1.3. BookService 인터페이스 생성

이 인터페이스를 구현하는 구현체는 만들지 않고, Mock 데이터를 이용하여 테스트 진행

publicinterfaceBookService {
List<Book> getBookList();
}

2.1.4. BookControllerTest 생성

@RunWith(SpringRunner.class)
@WebMvcTest(BookController.class)
publicclassBookControllerTest {
@AutowiredprivateMockMvcmvc;
@MockBeanprivateBookServicebookService;
@TestpublicvoidBook_MVC_TEST() throwsException {
Bookbook = newBook("Spring Boot Book", LocalDateTime.now());
given(bookService.getBookList()).willReturn(Collections.singletonList(book));
mvc.perform(get("/books"))
.andExpect(status().isOk()) // HTTP 상태값이 200인지 테스트
.andExpect(view().name("book")) // 반환되는 view의 이름이 book인지 테스트
.andExpect(model().attributeExists("bookList")) // Model의 프로퍼티 중 bookList라는 프로퍼티가 존재하는지 테스트
.andExpect(model().attribute("bookList", contains(book))); // Model의 프로퍼티 중 bookList 프로퍼티에 book 객체가 담겨져 있는지 테스트
}
}
  • @WebMvcTest를 사용하기 위해서는 테스트할 컨트롤러 이름을 명시해야 한다. (여기서는 BookContoller)
  • MockMvc는 모든 의존성을 로드하지 않고 BookContoller와 관련된 bean만 로드한다.
    • 여기서는 MockMvc를 주입시켰기 때문에 전체 HTTP서버를 실행하지 않고 테스트 가능
  • BookService를 구현한 구현체는 없지만 @MockBean으로 BookService를 가짜객체로 대체함
  • @DataJpaTest 는 JPA 테스트가 끝날 때 마다 자동으로 사용된 데이터를 롤백
  • EntityManager의 대체재로 만들어진 테스트용 TestEntityManager를 사용하여 persist, flush, find 등의 기본적인 JPA 테스트 가능

3. @DataJpaTest

JPA 관련 테스트 설정만 로드

  • DataSource의 설정이 정상적인지 테스트
  • JPA를 사용하여 데이터를 제대로 생성, 수정, 삭제하는지 테스트
  • 인메모리 임베디드 데이터베이스 사용
    • 메인 메모리를 데이터 저장소로 하여 DB를 어플리케이션에 내장하여 운용하는 DB
  • @Entity 클래스를 스캔하여 Spring Data JPA Repositories를 구성

3.1. 별도의 DataSource 사용하기

기본 설정된 DataSource를 사용하지 않도록 아래와 같이 설정

@RunWith(SpringRunner.class)
@DataJpaTest@ActiveProfiles("...")
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
publicclassJpaTest {
...
}

또는 application.yml 파일을 아래와 같이 수정

spring.test.database.replace: NONE

3.2. 테스트 데이터베이스 선택

3.2.1. 프로퍼티 설정

spring.test.database.connection: H2

3.2.2. 어노테이션 설정

@AutoConfigureTestDatabase(connection = H2)
...

3.3. 예시

3.3.1. Book 클래스에 JPA 관련 어노테이션 추가

@NoArgsConstructor@Getter@Entity@TablepublicclassBook {
@Id@GeneratedValueprivateIntegeridx;
@ColumnprivateStringtitle;
@ColumnprivateLocalDateTimepublishedAt;
@BuilderpublicBook(Stringtitle, LocalDateTimepublishedAt) {
this.title = title;
this.publishedAt = publishedAt;
}
}

3.3.2. BookRepository 생성

publicinterfaceBookRepositoryextendsJpaRepository<Book, Integer> {
}

3.3.3. @DataJpaTest로 테스트 수행하기

@RunWith(SpringRunner.class)
@DataJpaTestpublicclassBookJpaTest {
privatefinalstaticStringBOOT_TEST_TITLE = "Spring Boot Test Book";
@AutowiredprivateTestEntityManagertestEntityManager;
@AutowiredprivateBookRepositorybookRepository;
@TestpublicvoidBookList_Save_Test() {
Bookbook = Book.builder().title(BOOT_TEST_TITLE)
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book);
assertThat(bookRepository.getOne(book.getIdx()), is(book));
}
@TestpublicvoidBookList_Save_Search_Test() {
Bookbook1 = Book.builder().title(BOOT_TEST_TITLE + "1")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book1);
Bookbook2 = Book.builder().title(BOOT_TEST_TITLE + "2")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book2);
Bookbook3 = Book.builder().title(BOOT_TEST_TITLE + "3")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book3);
List<Book> bookList = bookRepository.findAll();
assertThat(bookList, hasSize(3));
assertThat(bookList, contains(book1, book2, book3));
}
@TestpublicvoidBookList_Save_Delete_Test() {
Bookbook1 = Book.builder().title(BOOT_TEST_TITLE + "1")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book1);
Bookbook2 = Book.builder().title(BOOT_TEST_TITLE + "2")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book2);
bookRepository.deleteAll();
assertThat(bookRepository.findAll(), IsEmptyCollection.empty());
}
}

4. @RestClientTest

REST 통신의 데이터형으로 사용되는 JSON 형식이 예상대로 응답을 반환하는지 등을 테스트

4.1. REST 테스트를 위한 BookRestController

@RestControllerpublicclassBookRestController {
@AutowiredprivateBookRestServicebookRestService;
@GetMapping(path = "/rest/test", produces = MediaType.APPLICATION_JSON_VALUE)
publicBookgetRestBooks() {
returnbookRestService.getRestBook();
}
}

getRestBook() 메서드의 반환값은 Book 객체이지만 @RestController로 설정되어 있으면 JSON 형식의 String형으로 반환된다.

4.2. REST 테스트용 BookRestService 생성

@ServicepublicclassBookRestService {
privatefinalRestTemplaterestTemplate;
publicBookRestService(RestTemplateBuilderrestTemplateBuilder) {
this.restTemplate = restTemplateBuilder.rootUri("/rest/test").build();
}
publicBookgetRestBook() {
returnthis.restTemplate.getForObject("/rest/test", Book.class);
}
}

4.3. @RestClientTest 를 사용한 REST 테스트 코드

@RunWith(SpringRunner.class)
@RestClientTest(BookRestService.class)
publicclassBookRestTest {
@RulepublicExpectedExceptionthrown = ExpectedException.none();
@AutowiredprivateBookRestServicebookRestService;
@AutowiredprivateMockRestServiceServerserver;
@Testpublicvoidrest_test() {
this.server.expect(requestTo("/rest/test"))
.andRespond(withSuccess(newClassPathResource("/test.json", getClass())
, MediaType.APPLICATION_JSON));
Bookbook = this.bookRestService.getRestBook();
assertThat(book.getTitle()).isEqualTo("테스트");
}
@Testpublicvoidrest_error_test() {
this.server.expect(requestTo("/rest/test"))
.andRespond(withServerError());
this.thrown.expect(HttpServerErrorException.class);
this.bookRestService.getRestBook();
}
}

4.4. 테스트 코드에 필요한 리소스 파일

{"idx":null,"title":"테스트","publishedAt":null}

5. @JsonTest

JSON의 직렬화(Serialization)와 역직렬화(Deserialization)를 수행하는 라이브러리인 Gson과 Jackson API의 테스트를 제공

JSON 테스트는 두 가지로 나뉜다. 문자열로 나열된 JSON 데이터를 객체로 변환하여 변환된 객체값을 테스트하거나 그 반대.

5.1. 예시

@RunWith(SpringRunner.class)
@JsonTestpublicclassBookJsonTest {
@AutowiredprivateJacksonTester<Book> json;
@Testpublicvoidjson_test() throwsException {
Bookbook = Book.builder()
.title("테스트")
.build();
Stringcontent = "{\"title\":\"테스트\"}";
assertThat(this.json.parseObject(content).getTitle()).isEqualTo(book.getTitle());
assertThat(this.json.parseObject(content).getPublishedAt()).isNull();
assertThat(this.json.write(book)).isEqualToJson("/test.json");
assertThat(this.json.write(book)).hasJsonPathStringValue("title");
assertThat(this.json.write(book)).extractingJsonPathStringValue("title").isEqualTo("테스트");
}
}

*Reference

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

History
393 lines (310 loc) · 12.4 KB

File metadata and controls

393 lines (310 loc) · 12.4 KB

Spring Boot Test 정리

1. @SpringBootTest

통합 테스트를 제공

  • 실제 구동되는 어플리케이션과 똑같이 ApplicationContext를 로드하여 테스트
    • 하고 싶은 테스트를 모두 수행 가능
    • 어플리케이션에 설정된 bean을 모두 로드하기 때문에 규모가 클수록 느리다. (단위 테스트가 무의미해짐)
  • 어플리케이션이 실행될 때의 설정을 임의로 바꾸어 테스트 진행 가능
  • 여러 단위 테스트를 하나의 통합된 테스트로 수행할 때 적합
  • 메인클래스와 함께 기본 제공
@RunWith(SpringRunner.class)
@SpringBootTestpublicclassDemoApplicationTests {
@TestpublicvoidcontextLoads() {
}
}

1.1 @RunWith

  • JUnit에 내장된 Runner를 사용하는 대신 어노테이션에 정의된 Runner 클래스 사용
  • @SpringBootTest 를 사용하려면 JUnit 실행에 필요한 SpringJUnit4ClassRunner 클래스를 상속받은 @RunWith(SpringRunner.class)를 붙여야 한다.

1.2. @SpringBootTest 의 파라미터들

value : 테스트가 실행되기 전에 적용할 프로퍼티 주입.(기존의 프로퍼티 오버라이드)
properties : 테스트가 실행되기 전에 {key=value} 형식으로 프로퍼티 추가.
classes : ApplicationContext에 로드할 클래스 지정. (지정하지 않으면 @SpringBootConfiguration을 찾아서 로드)
webEnvironment : 어플리케이션이 실행될 때의 웹 환경을 설정. (기본값은 Mock 서블릿을 로드하여 구동)

// value와 properties는 함께 사용할 수 없으므로 에러 발생@RunWith(SpringRunner.class)
@SpringBootTest(value = "value=test"
, properties = {"property.value=propertyTest"}
, classes = {DemoApplicationTests.class}
, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
publicclassDemoApplicationTests {
@Value("${value}")
privateStringvalue;
@Value("${property.value}")
privateStringpropertyValue;
@TestpublicvoidcontextLoads() {
assertThat(value, is("test"));
assertThat(propertyValue, is("propertyTest"));
}
}

1.3. 사용 팁

  • 프로파일(개발, QA, 운영) 마다 다른 DataSource를 갖는 경우, @ActiveProfiles("local") 을 사용
  • @Transactional을 사용하면 테스트를 마치고 나서 수정된 데이터가 롤백된다.
  • @SpringBootTest는 기본적으로 @SpringBootApplication 이나 @SpringBootConfiguration 을 찾는다. (둘 중 하나는 필수)

2. @WebMvcTest

MVC를 위한 테스트

  • 웹에서 테스트하기 힘든 Controller를 테스트하는데 적합
  • 웹상에서 요청과 응답에 대해 테스트할 수 있다.
  • Security와 Filter까지 자동으로 테스트하며, 수동으로 추가/삭제 가능
  • @WebMvcTest 를 사용하면 MVC 관련 설정들만 로드되기 때문에 가볍게 테스트 가능
    • MVC 관련 설정: @Controller, @ControllerAdvice, @JsonComponent, Filter, WebMvcConfigurer, HandlerMethodArgumentResolver

2.1. 예시

BookController에서 책 리스트를 받아오는 테스트

2.1.1.Book 클래스 생성

@NoArgsConstructor@GetterpublicclassBook {
privateIntegeridx;
privateStringtitle;
privateLocalDateTimepublishedAt;
@BuilderpublicBook(Stringtitle, LocalDateTimepublishedAt) {
this.title = title;
this.publishedAt = publishedAt;
}
}

2.1.2. BookController 클래스 생성

@ControllerpublicclassBookController {
@AutowiredprivateBookServicebookService;
@GetMapping("/books")
publicStringgetBookList(Modelmodel) {
model.addAttribute("bookList", bookService.getBookList());
return"book";
}
}

2.1.3. BookService 인터페이스 생성

이 인터페이스를 구현하는 구현체는 만들지 않고, Mock 데이터를 이용하여 테스트 진행

publicinterfaceBookService {
List<Book> getBookList();
}

2.1.4. BookControllerTest 생성

@RunWith(SpringRunner.class)
@WebMvcTest(BookController.class)
publicclassBookControllerTest {
@AutowiredprivateMockMvcmvc;
@MockBeanprivateBookServicebookService;
@TestpublicvoidBook_MVC_TEST() throwsException {
Bookbook = newBook("Spring Boot Book", LocalDateTime.now());
given(bookService.getBookList()).willReturn(Collections.singletonList(book));
mvc.perform(get("/books"))
.andExpect(status().isOk()) // HTTP 상태값이 200인지 테스트
.andExpect(view().name("book")) // 반환되는 view의 이름이 book인지 테스트
.andExpect(model().attributeExists("bookList")) // Model의 프로퍼티 중 bookList라는 프로퍼티가 존재하는지 테스트
.andExpect(model().attribute("bookList", contains(book))); // Model의 프로퍼티 중 bookList 프로퍼티에 book 객체가 담겨져 있는지 테스트
}
}
  • @WebMvcTest를 사용하기 위해서는 테스트할 컨트롤러 이름을 명시해야 한다. (여기서는 BookContoller)
  • MockMvc는 모든 의존성을 로드하지 않고 BookContoller와 관련된 bean만 로드한다.
    • 여기서는 MockMvc를 주입시켰기 때문에 전체 HTTP서버를 실행하지 않고 테스트 가능
  • BookService를 구현한 구현체는 없지만 @MockBean으로 BookService를 가짜객체로 대체함
  • @DataJpaTest 는 JPA 테스트가 끝날 때 마다 자동으로 사용된 데이터를 롤백
  • EntityManager의 대체재로 만들어진 테스트용 TestEntityManager를 사용하여 persist, flush, find 등의 기본적인 JPA 테스트 가능

3. @DataJpaTest

JPA 관련 테스트 설정만 로드

  • DataSource의 설정이 정상적인지 테스트
  • JPA를 사용하여 데이터를 제대로 생성, 수정, 삭제하는지 테스트
  • 인메모리 임베디드 데이터베이스 사용
    • 메인 메모리를 데이터 저장소로 하여 DB를 어플리케이션에 내장하여 운용하는 DB
  • @Entity 클래스를 스캔하여 Spring Data JPA Repositories를 구성

3.1. 별도의 DataSource 사용하기

기본 설정된 DataSource를 사용하지 않도록 아래와 같이 설정

@RunWith(SpringRunner.class)
@DataJpaTest@ActiveProfiles("...")
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
publicclassJpaTest {
...
}

또는 application.yml 파일을 아래와 같이 수정

spring.test.database.replace: NONE

3.2. 테스트 데이터베이스 선택

3.2.1. 프로퍼티 설정

spring.test.database.connection: H2

3.2.2. 어노테이션 설정

@AutoConfigureTestDatabase(connection = H2)
...

3.3. 예시

3.3.1. Book 클래스에 JPA 관련 어노테이션 추가

@NoArgsConstructor@Getter@Entity@TablepublicclassBook {
@Id@GeneratedValueprivateIntegeridx;
@ColumnprivateStringtitle;
@ColumnprivateLocalDateTimepublishedAt;
@BuilderpublicBook(Stringtitle, LocalDateTimepublishedAt) {
this.title = title;
this.publishedAt = publishedAt;
}
}

3.3.2. BookRepository 생성

publicinterfaceBookRepositoryextendsJpaRepository<Book, Integer> {
}

3.3.3. @DataJpaTest로 테스트 수행하기

@RunWith(SpringRunner.class)
@DataJpaTestpublicclassBookJpaTest {
privatefinalstaticStringBOOT_TEST_TITLE = "Spring Boot Test Book";
@AutowiredprivateTestEntityManagertestEntityManager;
@AutowiredprivateBookRepositorybookRepository;
@TestpublicvoidBookList_Save_Test() {
Bookbook = Book.builder().title(BOOT_TEST_TITLE)
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book);
assertThat(bookRepository.getOne(book.getIdx()), is(book));
}
@TestpublicvoidBookList_Save_Search_Test() {
Bookbook1 = Book.builder().title(BOOT_TEST_TITLE + "1")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book1);
Bookbook2 = Book.builder().title(BOOT_TEST_TITLE + "2")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book2);
Bookbook3 = Book.builder().title(BOOT_TEST_TITLE + "3")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book3);
List<Book> bookList = bookRepository.findAll();
assertThat(bookList, hasSize(3));
assertThat(bookList, contains(book1, book2, book3));
}
@TestpublicvoidBookList_Save_Delete_Test() {
Bookbook1 = Book.builder().title(BOOT_TEST_TITLE + "1")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book1);
Bookbook2 = Book.builder().title(BOOT_TEST_TITLE + "2")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book2);
bookRepository.deleteAll();
assertThat(bookRepository.findAll(), IsEmptyCollection.empty());
}
}

4. @RestClientTest

REST 통신의 데이터형으로 사용되는 JSON 형식이 예상대로 응답을 반환하는지 등을 테스트

4.1. REST 테스트를 위한 BookRestController

@RestControllerpublicclassBookRestController {
@AutowiredprivateBookRestServicebookRestService;
@GetMapping(path = "/rest/test", produces = MediaType.APPLICATION_JSON_VALUE)
publicBookgetRestBooks() {
returnbookRestService.getRestBook();
}
}

getRestBook() 메서드의 반환값은 Book 객체이지만 @RestController로 설정되어 있으면 JSON 형식의 String형으로 반환된다.

4.2. REST 테스트용 BookRestService 생성

@ServicepublicclassBookRestService {
privatefinalRestTemplaterestTemplate;
publicBookRestService(RestTemplateBuilderrestTemplateBuilder) {
this.restTemplate = restTemplateBuilder.rootUri("/rest/test").build();
}
publicBookgetRestBook() {
returnthis.restTemplate.getForObject("/rest/test", Book.class);
}
}

4.3. @RestClientTest 를 사용한 REST 테스트 코드

@RunWith(SpringRunner.class)
@RestClientTest(BookRestService.class)
publicclassBookRestTest {
@RulepublicExpectedExceptionthrown = ExpectedException.none();
@AutowiredprivateBookRestServicebookRestService;
@AutowiredprivateMockRestServiceServerserver;
@Testpublicvoidrest_test() {
this.server.expect(requestTo("/rest/test"))
.andRespond(withSuccess(newClassPathResource("/test.json", getClass())
, MediaType.APPLICATION_JSON));
Bookbook = this.bookRestService.getRestBook();
assertThat(book.getTitle()).isEqualTo("테스트");
}
@Testpublicvoidrest_error_test() {
this.server.expect(requestTo("/rest/test"))
.andRespond(withServerError());
this.thrown.expect(HttpServerErrorException.class);
this.bookRestService.getRestBook();
}
}

4.4. 테스트 코드에 필요한 리소스 파일

{"idx":null,"title":"테스트","publishedAt":null}

5. @JsonTest

JSON의 직렬화(Serialization)와 역직렬화(Deserialization)를 수행하는 라이브러리인 Gson과 Jackson API의 테스트를 제공

JSON 테스트는 두 가지로 나뉜다. 문자열로 나열된 JSON 데이터를 객체로 변환하여 변환된 객체값을 테스트하거나 그 반대.

5.1. 예시

@RunWith(SpringRunner.class)
@JsonTestpublicclassBookJsonTest {
@AutowiredprivateJacksonTester<Book> json;
@Testpublicvoidjson_test() throwsException {
Bookbook = Book.builder()
.title("테스트")
.build();
Stringcontent = "{\"title\":\"테스트\"}";
assertThat(this.json.parseObject(content).getTitle()).isEqualTo(book.getTitle());
assertThat(this.json.parseObject(content).getPublishedAt()).isNull();
assertThat(this.json.write(book)).isEqualToJson("/test.json");
assertThat(this.json.write(book)).hasJsonPathStringValue("title");
assertThat(this.json.write(book)).extractingJsonPathStringValue("title").isEqualTo("테스트");
}
}

*Reference

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

History
393 lines (310 loc) · 12.4 KB

File metadata and controls

393 lines (310 loc) · 12.4 KB

Spring Boot Test 정리

1. @SpringBootTest

통합 테스트를 제공

  • 실제 구동되는 어플리케이션과 똑같이 ApplicationContext를 로드하여 테스트
    • 하고 싶은 테스트를 모두 수행 가능
    • 어플리케이션에 설정된 bean을 모두 로드하기 때문에 규모가 클수록 느리다. (단위 테스트가 무의미해짐)
  • 어플리케이션이 실행될 때의 설정을 임의로 바꾸어 테스트 진행 가능
  • 여러 단위 테스트를 하나의 통합된 테스트로 수행할 때 적합
  • 메인클래스와 함께 기본 제공
@RunWith(SpringRunner.class)
@SpringBootTestpublicclassDemoApplicationTests {
@TestpublicvoidcontextLoads() {
}
}

1.1 @RunWith

  • JUnit에 내장된 Runner를 사용하는 대신 어노테이션에 정의된 Runner 클래스 사용
  • @SpringBootTest 를 사용하려면 JUnit 실행에 필요한 SpringJUnit4ClassRunner 클래스를 상속받은 @RunWith(SpringRunner.class)를 붙여야 한다.

1.2. @SpringBootTest 의 파라미터들

value : 테스트가 실행되기 전에 적용할 프로퍼티 주입.(기존의 프로퍼티 오버라이드)
properties : 테스트가 실행되기 전에 {key=value} 형식으로 프로퍼티 추가.
classes : ApplicationContext에 로드할 클래스 지정. (지정하지 않으면 @SpringBootConfiguration을 찾아서 로드)
webEnvironment : 어플리케이션이 실행될 때의 웹 환경을 설정. (기본값은 Mock 서블릿을 로드하여 구동)

// value와 properties는 함께 사용할 수 없으므로 에러 발생@RunWith(SpringRunner.class)
@SpringBootTest(value = "value=test"
, properties = {"property.value=propertyTest"}
, classes = {DemoApplicationTests.class}
, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
publicclassDemoApplicationTests {
@Value("${value}")
privateStringvalue;
@Value("${property.value}")
privateStringpropertyValue;
@TestpublicvoidcontextLoads() {
assertThat(value, is("test"));
assertThat(propertyValue, is("propertyTest"));
}
}

1.3. 사용 팁

  • 프로파일(개발, QA, 운영) 마다 다른 DataSource를 갖는 경우, @ActiveProfiles("local") 을 사용
  • @Transactional을 사용하면 테스트를 마치고 나서 수정된 데이터가 롤백된다.
  • @SpringBootTest는 기본적으로 @SpringBootApplication 이나 @SpringBootConfiguration 을 찾는다. (둘 중 하나는 필수)

2. @WebMvcTest

MVC를 위한 테스트

  • 웹에서 테스트하기 힘든 Controller를 테스트하는데 적합
  • 웹상에서 요청과 응답에 대해 테스트할 수 있다.
  • Security와 Filter까지 자동으로 테스트하며, 수동으로 추가/삭제 가능
  • @WebMvcTest 를 사용하면 MVC 관련 설정들만 로드되기 때문에 가볍게 테스트 가능
    • MVC 관련 설정: @Controller, @ControllerAdvice, @JsonComponent, Filter, WebMvcConfigurer, HandlerMethodArgumentResolver

2.1. 예시

BookController에서 책 리스트를 받아오는 테스트

2.1.1.Book 클래스 생성

@NoArgsConstructor@GetterpublicclassBook {
privateIntegeridx;
privateStringtitle;
privateLocalDateTimepublishedAt;
@BuilderpublicBook(Stringtitle, LocalDateTimepublishedAt) {
this.title = title;
this.publishedAt = publishedAt;
}
}

2.1.2. BookController 클래스 생성

@ControllerpublicclassBookController {
@AutowiredprivateBookServicebookService;
@GetMapping("/books")
publicStringgetBookList(Modelmodel) {
model.addAttribute("bookList", bookService.getBookList());
return"book";
}
}

2.1.3. BookService 인터페이스 생성

이 인터페이스를 구현하는 구현체는 만들지 않고, Mock 데이터를 이용하여 테스트 진행

publicinterfaceBookService {
List<Book> getBookList();
}

2.1.4. BookControllerTest 생성

@RunWith(SpringRunner.class)
@WebMvcTest(BookController.class)
publicclassBookControllerTest {
@AutowiredprivateMockMvcmvc;
@MockBeanprivateBookServicebookService;
@TestpublicvoidBook_MVC_TEST() throwsException {
Bookbook = newBook("Spring Boot Book", LocalDateTime.now());
given(bookService.getBookList()).willReturn(Collections.singletonList(book));
mvc.perform(get("/books"))
.andExpect(status().isOk()) // HTTP 상태값이 200인지 테스트
.andExpect(view().name("book")) // 반환되는 view의 이름이 book인지 테스트
.andExpect(model().attributeExists("bookList")) // Model의 프로퍼티 중 bookList라는 프로퍼티가 존재하는지 테스트
.andExpect(model().attribute("bookList", contains(book))); // Model의 프로퍼티 중 bookList 프로퍼티에 book 객체가 담겨져 있는지 테스트
}
}
  • @WebMvcTest를 사용하기 위해서는 테스트할 컨트롤러 이름을 명시해야 한다. (여기서는 BookContoller)
  • MockMvc는 모든 의존성을 로드하지 않고 BookContoller와 관련된 bean만 로드한다.
    • 여기서는 MockMvc를 주입시켰기 때문에 전체 HTTP서버를 실행하지 않고 테스트 가능
  • BookService를 구현한 구현체는 없지만 @MockBean으로 BookService를 가짜객체로 대체함
  • @DataJpaTest 는 JPA 테스트가 끝날 때 마다 자동으로 사용된 데이터를 롤백
  • EntityManager의 대체재로 만들어진 테스트용 TestEntityManager를 사용하여 persist, flush, find 등의 기본적인 JPA 테스트 가능

3. @DataJpaTest

JPA 관련 테스트 설정만 로드

  • DataSource의 설정이 정상적인지 테스트
  • JPA를 사용하여 데이터를 제대로 생성, 수정, 삭제하는지 테스트
  • 인메모리 임베디드 데이터베이스 사용
    • 메인 메모리를 데이터 저장소로 하여 DB를 어플리케이션에 내장하여 운용하는 DB
  • @Entity 클래스를 스캔하여 Spring Data JPA Repositories를 구성

3.1. 별도의 DataSource 사용하기

기본 설정된 DataSource를 사용하지 않도록 아래와 같이 설정

@RunWith(SpringRunner.class)
@DataJpaTest@ActiveProfiles("...")
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
publicclassJpaTest {
...
}

또는 application.yml 파일을 아래와 같이 수정

spring.test.database.replace: NONE

3.2. 테스트 데이터베이스 선택

3.2.1. 프로퍼티 설정

spring.test.database.connection: H2

3.2.2. 어노테이션 설정

@AutoConfigureTestDatabase(connection = H2)
...

3.3. 예시

3.3.1. Book 클래스에 JPA 관련 어노테이션 추가

@NoArgsConstructor@Getter@Entity@TablepublicclassBook {
@Id@GeneratedValueprivateIntegeridx;
@ColumnprivateStringtitle;
@ColumnprivateLocalDateTimepublishedAt;
@BuilderpublicBook(Stringtitle, LocalDateTimepublishedAt) {
this.title = title;
this.publishedAt = publishedAt;
}
}

3.3.2. BookRepository 생성

publicinterfaceBookRepositoryextendsJpaRepository<Book, Integer> {
}

3.3.3. @DataJpaTest로 테스트 수행하기

@RunWith(SpringRunner.class)
@DataJpaTestpublicclassBookJpaTest {
privatefinalstaticStringBOOT_TEST_TITLE = "Spring Boot Test Book";
@AutowiredprivateTestEntityManagertestEntityManager;
@AutowiredprivateBookRepositorybookRepository;
@TestpublicvoidBookList_Save_Test() {
Bookbook = Book.builder().title(BOOT_TEST_TITLE)
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book);
assertThat(bookRepository.getOne(book.getIdx()), is(book));
}
@TestpublicvoidBookList_Save_Search_Test() {
Bookbook1 = Book.builder().title(BOOT_TEST_TITLE + "1")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book1);
Bookbook2 = Book.builder().title(BOOT_TEST_TITLE + "2")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book2);
Bookbook3 = Book.builder().title(BOOT_TEST_TITLE + "3")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book3);
List<Book> bookList = bookRepository.findAll();
assertThat(bookList, hasSize(3));
assertThat(bookList, contains(book1, book2, book3));
}
@TestpublicvoidBookList_Save_Delete_Test() {
Bookbook1 = Book.builder().title(BOOT_TEST_TITLE + "1")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book1);
Bookbook2 = Book.builder().title(BOOT_TEST_TITLE + "2")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book2);
bookRepository.deleteAll();
assertThat(bookRepository.findAll(), IsEmptyCollection.empty());
}
}

4. @RestClientTest

REST 통신의 데이터형으로 사용되는 JSON 형식이 예상대로 응답을 반환하는지 등을 테스트

4.1. REST 테스트를 위한 BookRestController

@RestControllerpublicclassBookRestController {
@AutowiredprivateBookRestServicebookRestService;
@GetMapping(path = "/rest/test", produces = MediaType.APPLICATION_JSON_VALUE)
publicBookgetRestBooks() {
returnbookRestService.getRestBook();
}
}

getRestBook() 메서드의 반환값은 Book 객체이지만 @RestController로 설정되어 있으면 JSON 형식의 String형으로 반환된다.

4.2. REST 테스트용 BookRestService 생성

@ServicepublicclassBookRestService {
privatefinalRestTemplaterestTemplate;
publicBookRestService(RestTemplateBuilderrestTemplateBuilder) {
this.restTemplate = restTemplateBuilder.rootUri("/rest/test").build();
}
publicBookgetRestBook() {
returnthis.restTemplate.getForObject("/rest/test", Book.class);
}
}

4.3. @RestClientTest 를 사용한 REST 테스트 코드

@RunWith(SpringRunner.class)
@RestClientTest(BookRestService.class)
publicclassBookRestTest {
@RulepublicExpectedExceptionthrown = ExpectedException.none();
@AutowiredprivateBookRestServicebookRestService;
@AutowiredprivateMockRestServiceServerserver;
@Testpublicvoidrest_test() {
this.server.expect(requestTo("/rest/test"))
.andRespond(withSuccess(newClassPathResource("/test.json", getClass())
, MediaType.APPLICATION_JSON));
Bookbook = this.bookRestService.getRestBook();
assertThat(book.getTitle()).isEqualTo("테스트");
}
@Testpublicvoidrest_error_test() {
this.server.expect(requestTo("/rest/test"))
.andRespond(withServerError());
this.thrown.expect(HttpServerErrorException.class);
this.bookRestService.getRestBook();
}
}

4.4. 테스트 코드에 필요한 리소스 파일

{"idx":null,"title":"테스트","publishedAt":null}

5. @JsonTest

JSON의 직렬화(Serialization)와 역직렬화(Deserialization)를 수행하는 라이브러리인 Gson과 Jackson API의 테스트를 제공

JSON 테스트는 두 가지로 나뉜다. 문자열로 나열된 JSON 데이터를 객체로 변환하여 변환된 객체값을 테스트하거나 그 반대.

5.1. 예시

@RunWith(SpringRunner.class)
@JsonTestpublicclassBookJsonTest {
@AutowiredprivateJacksonTester<Book> json;
@Testpublicvoidjson_test() throwsException {
Bookbook = Book.builder()
.title("테스트")
.build();
Stringcontent = "{\"title\":\"테스트\"}";
assertThat(this.json.parseObject(content).getTitle()).isEqualTo(book.getTitle());
assertThat(this.json.parseObject(content).getPublishedAt()).isNull();
assertThat(this.json.write(book)).isEqualToJson("/test.json");
assertThat(this.json.write(book)).hasJsonPathStringValue("title");
assertThat(this.json.write(book)).extractingJsonPathStringValue("title").isEqualTo("테스트");
}
}

*Reference

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Latest commit

History

History
393 lines (310 loc) · 12.4 KB

File metadata and controls

393 lines (310 loc) · 12.4 KB

Spring Boot Test 정리

1. @SpringBootTest

통합 테스트를 제공

  • 실제 구동되는 어플리케이션과 똑같이 ApplicationContext를 로드하여 테스트
    • 하고 싶은 테스트를 모두 수행 가능
    • 어플리케이션에 설정된 bean을 모두 로드하기 때문에 규모가 클수록 느리다. (단위 테스트가 무의미해짐)
  • 어플리케이션이 실행될 때의 설정을 임의로 바꾸어 테스트 진행 가능
  • 여러 단위 테스트를 하나의 통합된 테스트로 수행할 때 적합
  • 메인클래스와 함께 기본 제공
@RunWith(SpringRunner.class)
@SpringBootTestpublicclassDemoApplicationTests {
@TestpublicvoidcontextLoads() {
}
}

1.1 @RunWith

  • JUnit에 내장된 Runner를 사용하는 대신 어노테이션에 정의된 Runner 클래스 사용
  • @SpringBootTest 를 사용하려면 JUnit 실행에 필요한 SpringJUnit4ClassRunner 클래스를 상속받은 @RunWith(SpringRunner.class)를 붙여야 한다.

1.2. @SpringBootTest 의 파라미터들

value : 테스트가 실행되기 전에 적용할 프로퍼티 주입.(기존의 프로퍼티 오버라이드)
properties : 테스트가 실행되기 전에 {key=value} 형식으로 프로퍼티 추가.
classes : ApplicationContext에 로드할 클래스 지정. (지정하지 않으면 @SpringBootConfiguration을 찾아서 로드)
webEnvironment : 어플리케이션이 실행될 때의 웹 환경을 설정. (기본값은 Mock 서블릿을 로드하여 구동)

// value와 properties는 함께 사용할 수 없으므로 에러 발생@RunWith(SpringRunner.class)
@SpringBootTest(value = "value=test"
, properties = {"property.value=propertyTest"}
, classes = {DemoApplicationTests.class}
, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
publicclassDemoApplicationTests {
@Value("${value}")
privateStringvalue;
@Value("${property.value}")
privateStringpropertyValue;
@TestpublicvoidcontextLoads() {
assertThat(value, is("test"));
assertThat(propertyValue, is("propertyTest"));
}
}

1.3. 사용 팁

  • 프로파일(개발, QA, 운영) 마다 다른 DataSource를 갖는 경우, @ActiveProfiles("local") 을 사용
  • @Transactional을 사용하면 테스트를 마치고 나서 수정된 데이터가 롤백된다.
  • @SpringBootTest는 기본적으로 @SpringBootApplication 이나 @SpringBootConfiguration 을 찾는다. (둘 중 하나는 필수)

2. @WebMvcTest

MVC를 위한 테스트

  • 웹에서 테스트하기 힘든 Controller를 테스트하는데 적합
  • 웹상에서 요청과 응답에 대해 테스트할 수 있다.
  • Security와 Filter까지 자동으로 테스트하며, 수동으로 추가/삭제 가능
  • @WebMvcTest 를 사용하면 MVC 관련 설정들만 로드되기 때문에 가볍게 테스트 가능
    • MVC 관련 설정: @Controller, @ControllerAdvice, @JsonComponent, Filter, WebMvcConfigurer, HandlerMethodArgumentResolver

2.1. 예시

BookController에서 책 리스트를 받아오는 테스트

2.1.1.Book 클래스 생성

@NoArgsConstructor@GetterpublicclassBook {
privateIntegeridx;
privateStringtitle;
privateLocalDateTimepublishedAt;
@BuilderpublicBook(Stringtitle, LocalDateTimepublishedAt) {
this.title = title;
this.publishedAt = publishedAt;
}
}

2.1.2. BookController 클래스 생성

@ControllerpublicclassBookController {
@AutowiredprivateBookServicebookService;
@GetMapping("/books")
publicStringgetBookList(Modelmodel) {
model.addAttribute("bookList", bookService.getBookList());
return"book";
}
}

2.1.3. BookService 인터페이스 생성

이 인터페이스를 구현하는 구현체는 만들지 않고, Mock 데이터를 이용하여 테스트 진행

publicinterfaceBookService {
List<Book> getBookList();
}

2.1.4. BookControllerTest 생성

@RunWith(SpringRunner.class)
@WebMvcTest(BookController.class)
publicclassBookControllerTest {
@AutowiredprivateMockMvcmvc;
@MockBeanprivateBookServicebookService;
@TestpublicvoidBook_MVC_TEST() throwsException {
Bookbook = newBook("Spring Boot Book", LocalDateTime.now());
given(bookService.getBookList()).willReturn(Collections.singletonList(book));
mvc.perform(get("/books"))
.andExpect(status().isOk()) // HTTP 상태값이 200인지 테스트
.andExpect(view().name("book")) // 반환되는 view의 이름이 book인지 테스트
.andExpect(model().attributeExists("bookList")) // Model의 프로퍼티 중 bookList라는 프로퍼티가 존재하는지 테스트
.andExpect(model().attribute("bookList", contains(book))); // Model의 프로퍼티 중 bookList 프로퍼티에 book 객체가 담겨져 있는지 테스트
}
}
  • @WebMvcTest를 사용하기 위해서는 테스트할 컨트롤러 이름을 명시해야 한다. (여기서는 BookContoller)
  • MockMvc는 모든 의존성을 로드하지 않고 BookContoller와 관련된 bean만 로드한다.
    • 여기서는 MockMvc를 주입시켰기 때문에 전체 HTTP서버를 실행하지 않고 테스트 가능
  • BookService를 구현한 구현체는 없지만 @MockBean으로 BookService를 가짜객체로 대체함
  • @DataJpaTest 는 JPA 테스트가 끝날 때 마다 자동으로 사용된 데이터를 롤백
  • EntityManager의 대체재로 만들어진 테스트용 TestEntityManager를 사용하여 persist, flush, find 등의 기본적인 JPA 테스트 가능

3. @DataJpaTest

JPA 관련 테스트 설정만 로드

  • DataSource의 설정이 정상적인지 테스트
  • JPA를 사용하여 데이터를 제대로 생성, 수정, 삭제하는지 테스트
  • 인메모리 임베디드 데이터베이스 사용
    • 메인 메모리를 데이터 저장소로 하여 DB를 어플리케이션에 내장하여 운용하는 DB
  • @Entity 클래스를 스캔하여 Spring Data JPA Repositories를 구성

3.1. 별도의 DataSource 사용하기

기본 설정된 DataSource를 사용하지 않도록 아래와 같이 설정

@RunWith(SpringRunner.class)
@DataJpaTest@ActiveProfiles("...")
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
publicclassJpaTest {
...
}

또는 application.yml 파일을 아래와 같이 수정

spring.test.database.replace: NONE

3.2. 테스트 데이터베이스 선택

3.2.1. 프로퍼티 설정

spring.test.database.connection: H2

3.2.2. 어노테이션 설정

@AutoConfigureTestDatabase(connection = H2)
...

3.3. 예시

3.3.1. Book 클래스에 JPA 관련 어노테이션 추가

@NoArgsConstructor@Getter@Entity@TablepublicclassBook {
@Id@GeneratedValueprivateIntegeridx;
@ColumnprivateStringtitle;
@ColumnprivateLocalDateTimepublishedAt;
@BuilderpublicBook(Stringtitle, LocalDateTimepublishedAt) {
this.title = title;
this.publishedAt = publishedAt;
}
}

3.3.2. BookRepository 생성

publicinterfaceBookRepositoryextendsJpaRepository<Book, Integer> {
}

3.3.3. @DataJpaTest로 테스트 수행하기

@RunWith(SpringRunner.class)
@DataJpaTestpublicclassBookJpaTest {
privatefinalstaticStringBOOT_TEST_TITLE = "Spring Boot Test Book";
@AutowiredprivateTestEntityManagertestEntityManager;
@AutowiredprivateBookRepositorybookRepository;
@TestpublicvoidBookList_Save_Test() {
Bookbook = Book.builder().title(BOOT_TEST_TITLE)
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book);
assertThat(bookRepository.getOne(book.getIdx()), is(book));
}
@TestpublicvoidBookList_Save_Search_Test() {
Bookbook1 = Book.builder().title(BOOT_TEST_TITLE + "1")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book1);
Bookbook2 = Book.builder().title(BOOT_TEST_TITLE + "2")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book2);
Bookbook3 = Book.builder().title(BOOT_TEST_TITLE + "3")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book3);
List<Book> bookList = bookRepository.findAll();
assertThat(bookList, hasSize(3));
assertThat(bookList, contains(book1, book2, book3));
}
@TestpublicvoidBookList_Save_Delete_Test() {
Bookbook1 = Book.builder().title(BOOT_TEST_TITLE + "1")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book1);
Bookbook2 = Book.builder().title(BOOT_TEST_TITLE + "2")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book2);
bookRepository.deleteAll();
assertThat(bookRepository.findAll(), IsEmptyCollection.empty());
}
}

4. @RestClientTest

REST 통신의 데이터형으로 사용되는 JSON 형식이 예상대로 응답을 반환하는지 등을 테스트

4.1. REST 테스트를 위한 BookRestController

@RestControllerpublicclassBookRestController {
@AutowiredprivateBookRestServicebookRestService;
@GetMapping(path = "/rest/test", produces = MediaType.APPLICATION_JSON_VALUE)
publicBookgetRestBooks() {
returnbookRestService.getRestBook();
}
}

getRestBook() 메서드의 반환값은 Book 객체이지만 @RestController로 설정되어 있으면 JSON 형식의 String형으로 반환된다.

4.2. REST 테스트용 BookRestService 생성

@ServicepublicclassBookRestService {
privatefinalRestTemplaterestTemplate;
publicBookRestService(RestTemplateBuilderrestTemplateBuilder) {
this.restTemplate = restTemplateBuilder.rootUri("/rest/test").build();
}
publicBookgetRestBook() {
returnthis.restTemplate.getForObject("/rest/test", Book.class);
}
}

4.3. @RestClientTest 를 사용한 REST 테스트 코드

@RunWith(SpringRunner.class)
@RestClientTest(BookRestService.class)
publicclassBookRestTest {
@RulepublicExpectedExceptionthrown = ExpectedException.none();
@AutowiredprivateBookRestServicebookRestService;
@AutowiredprivateMockRestServiceServerserver;
@Testpublicvoidrest_test() {
this.server.expect(requestTo("/rest/test"))
.andRespond(withSuccess(newClassPathResource("/test.json", getClass())
, MediaType.APPLICATION_JSON));
Bookbook = this.bookRestService.getRestBook();
assertThat(book.getTitle()).isEqualTo("테스트");
}
@Testpublicvoidrest_error_test() {
this.server.expect(requestTo("/rest/test"))
.andRespond(withServerError());
this.thrown.expect(HttpServerErrorException.class);
this.bookRestService.getRestBook();
}
}

4.4. 테스트 코드에 필요한 리소스 파일

{"idx":null,"title":"테스트","publishedAt":null}

5. @JsonTest

JSON의 직렬화(Serialization)와 역직렬화(Deserialization)를 수행하는 라이브러리인 Gson과 Jackson API의 테스트를 제공

JSON 테스트는 두 가지로 나뉜다. 문자열로 나열된 JSON 데이터를 객체로 변환하여 변환된 객체값을 테스트하거나 그 반대.

5.1. 예시

@RunWith(SpringRunner.class)
@JsonTestpublicclassBookJsonTest {
@AutowiredprivateJacksonTester<Book> json;
@Testpublicvoidjson_test() throwsException {
Bookbook = Book.builder()
.title("테스트")
.build();
Stringcontent = "{\"title\":\"테스트\"}";
assertThat(this.json.parseObject(content).getTitle()).isEqualTo(book.getTitle());
assertThat(this.json.parseObject(content).getPublishedAt()).isNull();
assertThat(this.json.write(book)).isEqualToJson("/test.json");
assertThat(this.json.write(book)).hasJsonPathStringValue("title");
assertThat(this.json.write(book)).extractingJsonPathStringValue("title").isEqualTo("테스트");
}
}

*Reference

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

History
393 lines (310 loc) · 12.4 KB

File metadata and controls

393 lines (310 loc) · 12.4 KB

Spring Boot Test 정리

1. @SpringBootTest

통합 테스트를 제공

  • 실제 구동되는 어플리케이션과 똑같이 ApplicationContext를 로드하여 테스트
    • 하고 싶은 테스트를 모두 수행 가능
    • 어플리케이션에 설정된 bean을 모두 로드하기 때문에 규모가 클수록 느리다. (단위 테스트가 무의미해짐)
  • 어플리케이션이 실행될 때의 설정을 임의로 바꾸어 테스트 진행 가능
  • 여러 단위 테스트를 하나의 통합된 테스트로 수행할 때 적합
  • 메인클래스와 함께 기본 제공
@RunWith(SpringRunner.class)
@SpringBootTestpublicclassDemoApplicationTests {
@TestpublicvoidcontextLoads() {
}
}

1.1 @RunWith

  • JUnit에 내장된 Runner를 사용하는 대신 어노테이션에 정의된 Runner 클래스 사용
  • @SpringBootTest 를 사용하려면 JUnit 실행에 필요한 SpringJUnit4ClassRunner 클래스를 상속받은 @RunWith(SpringRunner.class)를 붙여야 한다.

1.2. @SpringBootTest 의 파라미터들

value : 테스트가 실행되기 전에 적용할 프로퍼티 주입.(기존의 프로퍼티 오버라이드)
properties : 테스트가 실행되기 전에 {key=value} 형식으로 프로퍼티 추가.
classes : ApplicationContext에 로드할 클래스 지정. (지정하지 않으면 @SpringBootConfiguration을 찾아서 로드)
webEnvironment : 어플리케이션이 실행될 때의 웹 환경을 설정. (기본값은 Mock 서블릿을 로드하여 구동)

// value와 properties는 함께 사용할 수 없으므로 에러 발생@RunWith(SpringRunner.class)
@SpringBootTest(value = "value=test"
, properties = {"property.value=propertyTest"}
, classes = {DemoApplicationTests.class}
, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
publicclassDemoApplicationTests {
@Value("${value}")
privateStringvalue;
@Value("${property.value}")
privateStringpropertyValue;
@TestpublicvoidcontextLoads() {
assertThat(value, is("test"));
assertThat(propertyValue, is("propertyTest"));
}
}

1.3. 사용 팁

  • 프로파일(개발, QA, 운영) 마다 다른 DataSource를 갖는 경우, @ActiveProfiles("local") 을 사용
  • @Transactional을 사용하면 테스트를 마치고 나서 수정된 데이터가 롤백된다.
  • @SpringBootTest는 기본적으로 @SpringBootApplication 이나 @SpringBootConfiguration 을 찾는다. (둘 중 하나는 필수)

2. @WebMvcTest

MVC를 위한 테스트

  • 웹에서 테스트하기 힘든 Controller를 테스트하는데 적합
  • 웹상에서 요청과 응답에 대해 테스트할 수 있다.
  • Security와 Filter까지 자동으로 테스트하며, 수동으로 추가/삭제 가능
  • @WebMvcTest 를 사용하면 MVC 관련 설정들만 로드되기 때문에 가볍게 테스트 가능
    • MVC 관련 설정: @Controller, @ControllerAdvice, @JsonComponent, Filter, WebMvcConfigurer, HandlerMethodArgumentResolver

2.1. 예시

BookController에서 책 리스트를 받아오는 테스트

2.1.1.Book 클래스 생성

@NoArgsConstructor@GetterpublicclassBook {
privateIntegeridx;
privateStringtitle;
privateLocalDateTimepublishedAt;
@BuilderpublicBook(Stringtitle, LocalDateTimepublishedAt) {
this.title = title;
this.publishedAt = publishedAt;
}
}

2.1.2. BookController 클래스 생성

@ControllerpublicclassBookController {
@AutowiredprivateBookServicebookService;
@GetMapping("/books")
publicStringgetBookList(Modelmodel) {
model.addAttribute("bookList", bookService.getBookList());
return"book";
}
}

2.1.3. BookService 인터페이스 생성

이 인터페이스를 구현하는 구현체는 만들지 않고, Mock 데이터를 이용하여 테스트 진행

publicinterfaceBookService {
List<Book> getBookList();
}

2.1.4. BookControllerTest 생성

@RunWith(SpringRunner.class)
@WebMvcTest(BookController.class)
publicclassBookControllerTest {
@AutowiredprivateMockMvcmvc;
@MockBeanprivateBookServicebookService;
@TestpublicvoidBook_MVC_TEST() throwsException {
Bookbook = newBook("Spring Boot Book", LocalDateTime.now());
given(bookService.getBookList()).willReturn(Collections.singletonList(book));
mvc.perform(get("/books"))
.andExpect(status().isOk()) // HTTP 상태값이 200인지 테스트
.andExpect(view().name("book")) // 반환되는 view의 이름이 book인지 테스트
.andExpect(model().attributeExists("bookList")) // Model의 프로퍼티 중 bookList라는 프로퍼티가 존재하는지 테스트
.andExpect(model().attribute("bookList", contains(book))); // Model의 프로퍼티 중 bookList 프로퍼티에 book 객체가 담겨져 있는지 테스트
}
}
  • @WebMvcTest를 사용하기 위해서는 테스트할 컨트롤러 이름을 명시해야 한다. (여기서는 BookContoller)
  • MockMvc는 모든 의존성을 로드하지 않고 BookContoller와 관련된 bean만 로드한다.
    • 여기서는 MockMvc를 주입시켰기 때문에 전체 HTTP서버를 실행하지 않고 테스트 가능
  • BookService를 구현한 구현체는 없지만 @MockBean으로 BookService를 가짜객체로 대체함
  • @DataJpaTest 는 JPA 테스트가 끝날 때 마다 자동으로 사용된 데이터를 롤백
  • EntityManager의 대체재로 만들어진 테스트용 TestEntityManager를 사용하여 persist, flush, find 등의 기본적인 JPA 테스트 가능

3. @DataJpaTest

JPA 관련 테스트 설정만 로드

  • DataSource의 설정이 정상적인지 테스트
  • JPA를 사용하여 데이터를 제대로 생성, 수정, 삭제하는지 테스트
  • 인메모리 임베디드 데이터베이스 사용
    • 메인 메모리를 데이터 저장소로 하여 DB를 어플리케이션에 내장하여 운용하는 DB
  • @Entity 클래스를 스캔하여 Spring Data JPA Repositories를 구성

3.1. 별도의 DataSource 사용하기

기본 설정된 DataSource를 사용하지 않도록 아래와 같이 설정

@RunWith(SpringRunner.class)
@DataJpaTest@ActiveProfiles("...")
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
publicclassJpaTest {
...
}

또는 application.yml 파일을 아래와 같이 수정

spring.test.database.replace: NONE

3.2. 테스트 데이터베이스 선택

3.2.1. 프로퍼티 설정

spring.test.database.connection: H2

3.2.2. 어노테이션 설정

@AutoConfigureTestDatabase(connection = H2)
...

3.3. 예시

3.3.1. Book 클래스에 JPA 관련 어노테이션 추가

@NoArgsConstructor@Getter@Entity@TablepublicclassBook {
@Id@GeneratedValueprivateIntegeridx;
@ColumnprivateStringtitle;
@ColumnprivateLocalDateTimepublishedAt;
@BuilderpublicBook(Stringtitle, LocalDateTimepublishedAt) {
this.title = title;
this.publishedAt = publishedAt;
}
}

3.3.2. BookRepository 생성

publicinterfaceBookRepositoryextendsJpaRepository<Book, Integer> {
}

3.3.3. @DataJpaTest로 테스트 수행하기

@RunWith(SpringRunner.class)
@DataJpaTestpublicclassBookJpaTest {
privatefinalstaticStringBOOT_TEST_TITLE = "Spring Boot Test Book";
@AutowiredprivateTestEntityManagertestEntityManager;
@AutowiredprivateBookRepositorybookRepository;
@TestpublicvoidBookList_Save_Test() {
Bookbook = Book.builder().title(BOOT_TEST_TITLE)
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book);
assertThat(bookRepository.getOne(book.getIdx()), is(book));
}
@TestpublicvoidBookList_Save_Search_Test() {
Bookbook1 = Book.builder().title(BOOT_TEST_TITLE + "1")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book1);
Bookbook2 = Book.builder().title(BOOT_TEST_TITLE + "2")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book2);
Bookbook3 = Book.builder().title(BOOT_TEST_TITLE + "3")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book3);
List<Book> bookList = bookRepository.findAll();
assertThat(bookList, hasSize(3));
assertThat(bookList, contains(book1, book2, book3));
}
@TestpublicvoidBookList_Save_Delete_Test() {
Bookbook1 = Book.builder().title(BOOT_TEST_TITLE + "1")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book1);
Bookbook2 = Book.builder().title(BOOT_TEST_TITLE + "2")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book2);
bookRepository.deleteAll();
assertThat(bookRepository.findAll(), IsEmptyCollection.empty());
}
}

4. @RestClientTest

REST 통신의 데이터형으로 사용되는 JSON 형식이 예상대로 응답을 반환하는지 등을 테스트

4.1. REST 테스트를 위한 BookRestController

@RestControllerpublicclassBookRestController {
@AutowiredprivateBookRestServicebookRestService;
@GetMapping(path = "/rest/test", produces = MediaType.APPLICATION_JSON_VALUE)
publicBookgetRestBooks() {
returnbookRestService.getRestBook();
}
}

getRestBook() 메서드의 반환값은 Book 객체이지만 @RestController로 설정되어 있으면 JSON 형식의 String형으로 반환된다.

4.2. REST 테스트용 BookRestService 생성

@ServicepublicclassBookRestService {
privatefinalRestTemplaterestTemplate;
publicBookRestService(RestTemplateBuilderrestTemplateBuilder) {
this.restTemplate = restTemplateBuilder.rootUri("/rest/test").build();
}
publicBookgetRestBook() {
returnthis.restTemplate.getForObject("/rest/test", Book.class);
}
}

4.3. @RestClientTest 를 사용한 REST 테스트 코드

@RunWith(SpringRunner.class)
@RestClientTest(BookRestService.class)
publicclassBookRestTest {
@RulepublicExpectedExceptionthrown = ExpectedException.none();
@AutowiredprivateBookRestServicebookRestService;
@AutowiredprivateMockRestServiceServerserver;
@Testpublicvoidrest_test() {
this.server.expect(requestTo("/rest/test"))
.andRespond(withSuccess(newClassPathResource("/test.json", getClass())
, MediaType.APPLICATION_JSON));
Bookbook = this.bookRestService.getRestBook();
assertThat(book.getTitle()).isEqualTo("테스트");
}
@Testpublicvoidrest_error_test() {
this.server.expect(requestTo("/rest/test"))
.andRespond(withServerError());
this.thrown.expect(HttpServerErrorException.class);
this.bookRestService.getRestBook();
}
}

4.4. 테스트 코드에 필요한 리소스 파일

{"idx":null,"title":"테스트","publishedAt":null}

5. @JsonTest

JSON의 직렬화(Serialization)와 역직렬화(Deserialization)를 수행하는 라이브러리인 Gson과 Jackson API의 테스트를 제공

JSON 테스트는 두 가지로 나뉜다. 문자열로 나열된 JSON 데이터를 객체로 변환하여 변환된 객체값을 테스트하거나 그 반대.

5.1. 예시

@RunWith(SpringRunner.class)
@JsonTestpublicclassBookJsonTest {
@AutowiredprivateJacksonTester<Book> json;
@Testpublicvoidjson_test() throwsException {
Bookbook = Book.builder()
.title("테스트")
.build();
Stringcontent = "{\"title\":\"테스트\"}";
assertThat(this.json.parseObject(content).getTitle()).isEqualTo(book.getTitle());
assertThat(this.json.parseObject(content).getPublishedAt()).isNull();
assertThat(this.json.write(book)).isEqualToJson("/test.json");
assertThat(this.json.write(book)).hasJsonPathStringValue("title");
assertThat(this.json.write(book)).extractingJsonPathStringValue("title").isEqualTo("테스트");
}
}

*Reference

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

History
393 lines (310 loc) · 12.4 KB

File metadata and controls

393 lines (310 loc) · 12.4 KB

Spring Boot Test 정리

1. @SpringBootTest

통합 테스트를 제공

  • 실제 구동되는 어플리케이션과 똑같이 ApplicationContext를 로드하여 테스트
    • 하고 싶은 테스트를 모두 수행 가능
    • 어플리케이션에 설정된 bean을 모두 로드하기 때문에 규모가 클수록 느리다. (단위 테스트가 무의미해짐)
  • 어플리케이션이 실행될 때의 설정을 임의로 바꾸어 테스트 진행 가능
  • 여러 단위 테스트를 하나의 통합된 테스트로 수행할 때 적합
  • 메인클래스와 함께 기본 제공
@RunWith(SpringRunner.class)
@SpringBootTestpublicclassDemoApplicationTests {
@TestpublicvoidcontextLoads() {
}
}

1.1 @RunWith

  • JUnit에 내장된 Runner를 사용하는 대신 어노테이션에 정의된 Runner 클래스 사용
  • @SpringBootTest 를 사용하려면 JUnit 실행에 필요한 SpringJUnit4ClassRunner 클래스를 상속받은 @RunWith(SpringRunner.class)를 붙여야 한다.

1.2. @SpringBootTest 의 파라미터들

value : 테스트가 실행되기 전에 적용할 프로퍼티 주입.(기존의 프로퍼티 오버라이드)
properties : 테스트가 실행되기 전에 {key=value} 형식으로 프로퍼티 추가.
classes : ApplicationContext에 로드할 클래스 지정. (지정하지 않으면 @SpringBootConfiguration을 찾아서 로드)
webEnvironment : 어플리케이션이 실행될 때의 웹 환경을 설정. (기본값은 Mock 서블릿을 로드하여 구동)

// value와 properties는 함께 사용할 수 없으므로 에러 발생@RunWith(SpringRunner.class)
@SpringBootTest(value = "value=test"
, properties = {"property.value=propertyTest"}
, classes = {DemoApplicationTests.class}
, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
publicclassDemoApplicationTests {
@Value("${value}")
privateStringvalue;
@Value("${property.value}")
privateStringpropertyValue;
@TestpublicvoidcontextLoads() {
assertThat(value, is("test"));
assertThat(propertyValue, is("propertyTest"));
}
}

1.3. 사용 팁

  • 프로파일(개발, QA, 운영) 마다 다른 DataSource를 갖는 경우, @ActiveProfiles("local") 을 사용
  • @Transactional을 사용하면 테스트를 마치고 나서 수정된 데이터가 롤백된다.
  • @SpringBootTest는 기본적으로 @SpringBootApplication 이나 @SpringBootConfiguration 을 찾는다. (둘 중 하나는 필수)

2. @WebMvcTest

MVC를 위한 테스트

  • 웹에서 테스트하기 힘든 Controller를 테스트하는데 적합
  • 웹상에서 요청과 응답에 대해 테스트할 수 있다.
  • Security와 Filter까지 자동으로 테스트하며, 수동으로 추가/삭제 가능
  • @WebMvcTest 를 사용하면 MVC 관련 설정들만 로드되기 때문에 가볍게 테스트 가능
    • MVC 관련 설정: @Controller, @ControllerAdvice, @JsonComponent, Filter, WebMvcConfigurer, HandlerMethodArgumentResolver

2.1. 예시

BookController에서 책 리스트를 받아오는 테스트

2.1.1.Book 클래스 생성

@NoArgsConstructor@GetterpublicclassBook {
privateIntegeridx;
privateStringtitle;
privateLocalDateTimepublishedAt;
@BuilderpublicBook(Stringtitle, LocalDateTimepublishedAt) {
this.title = title;
this.publishedAt = publishedAt;
}
}

2.1.2. BookController 클래스 생성

@ControllerpublicclassBookController {
@AutowiredprivateBookServicebookService;
@GetMapping("/books")
publicStringgetBookList(Modelmodel) {
model.addAttribute("bookList", bookService.getBookList());
return"book";
}
}

2.1.3. BookService 인터페이스 생성

이 인터페이스를 구현하는 구현체는 만들지 않고, Mock 데이터를 이용하여 테스트 진행

publicinterfaceBookService {
List<Book> getBookList();
}

2.1.4. BookControllerTest 생성

@RunWith(SpringRunner.class)
@WebMvcTest(BookController.class)
publicclassBookControllerTest {
@AutowiredprivateMockMvcmvc;
@MockBeanprivateBookServicebookService;
@TestpublicvoidBook_MVC_TEST() throwsException {
Bookbook = newBook("Spring Boot Book", LocalDateTime.now());
given(bookService.getBookList()).willReturn(Collections.singletonList(book));
mvc.perform(get("/books"))
.andExpect(status().isOk()) // HTTP 상태값이 200인지 테스트
.andExpect(view().name("book")) // 반환되는 view의 이름이 book인지 테스트
.andExpect(model().attributeExists("bookList")) // Model의 프로퍼티 중 bookList라는 프로퍼티가 존재하는지 테스트
.andExpect(model().attribute("bookList", contains(book))); // Model의 프로퍼티 중 bookList 프로퍼티에 book 객체가 담겨져 있는지 테스트
}
}
  • @WebMvcTest를 사용하기 위해서는 테스트할 컨트롤러 이름을 명시해야 한다. (여기서는 BookContoller)
  • MockMvc는 모든 의존성을 로드하지 않고 BookContoller와 관련된 bean만 로드한다.
    • 여기서는 MockMvc를 주입시켰기 때문에 전체 HTTP서버를 실행하지 않고 테스트 가능
  • BookService를 구현한 구현체는 없지만 @MockBean으로 BookService를 가짜객체로 대체함
  • @DataJpaTest 는 JPA 테스트가 끝날 때 마다 자동으로 사용된 데이터를 롤백
  • EntityManager의 대체재로 만들어진 테스트용 TestEntityManager를 사용하여 persist, flush, find 등의 기본적인 JPA 테스트 가능

3. @DataJpaTest

JPA 관련 테스트 설정만 로드

  • DataSource의 설정이 정상적인지 테스트
  • JPA를 사용하여 데이터를 제대로 생성, 수정, 삭제하는지 테스트
  • 인메모리 임베디드 데이터베이스 사용
    • 메인 메모리를 데이터 저장소로 하여 DB를 어플리케이션에 내장하여 운용하는 DB
  • @Entity 클래스를 스캔하여 Spring Data JPA Repositories를 구성

3.1. 별도의 DataSource 사용하기

기본 설정된 DataSource를 사용하지 않도록 아래와 같이 설정

@RunWith(SpringRunner.class)
@DataJpaTest@ActiveProfiles("...")
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
publicclassJpaTest {
...
}

또는 application.yml 파일을 아래와 같이 수정

spring.test.database.replace: NONE

3.2. 테스트 데이터베이스 선택

3.2.1. 프로퍼티 설정

spring.test.database.connection: H2

3.2.2. 어노테이션 설정

@AutoConfigureTestDatabase(connection = H2)
...

3.3. 예시

3.3.1. Book 클래스에 JPA 관련 어노테이션 추가

@NoArgsConstructor@Getter@Entity@TablepublicclassBook {
@Id@GeneratedValueprivateIntegeridx;
@ColumnprivateStringtitle;
@ColumnprivateLocalDateTimepublishedAt;
@BuilderpublicBook(Stringtitle, LocalDateTimepublishedAt) {
this.title = title;
this.publishedAt = publishedAt;
}
}

3.3.2. BookRepository 생성

publicinterfaceBookRepositoryextendsJpaRepository<Book, Integer> {
}

3.3.3. @DataJpaTest로 테스트 수행하기

@RunWith(SpringRunner.class)
@DataJpaTestpublicclassBookJpaTest {
privatefinalstaticStringBOOT_TEST_TITLE = "Spring Boot Test Book";
@AutowiredprivateTestEntityManagertestEntityManager;
@AutowiredprivateBookRepositorybookRepository;
@TestpublicvoidBookList_Save_Test() {
Bookbook = Book.builder().title(BOOT_TEST_TITLE)
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book);
assertThat(bookRepository.getOne(book.getIdx()), is(book));
}
@TestpublicvoidBookList_Save_Search_Test() {
Bookbook1 = Book.builder().title(BOOT_TEST_TITLE + "1")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book1);
Bookbook2 = Book.builder().title(BOOT_TEST_TITLE + "2")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book2);
Bookbook3 = Book.builder().title(BOOT_TEST_TITLE + "3")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book3);
List<Book> bookList = bookRepository.findAll();
assertThat(bookList, hasSize(3));
assertThat(bookList, contains(book1, book2, book3));
}
@TestpublicvoidBookList_Save_Delete_Test() {
Bookbook1 = Book.builder().title(BOOT_TEST_TITLE + "1")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book1);
Bookbook2 = Book.builder().title(BOOT_TEST_TITLE + "2")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book2);
bookRepository.deleteAll();
assertThat(bookRepository.findAll(), IsEmptyCollection.empty());
}
}

4. @RestClientTest

REST 통신의 데이터형으로 사용되는 JSON 형식이 예상대로 응답을 반환하는지 등을 테스트

4.1. REST 테스트를 위한 BookRestController

@RestControllerpublicclassBookRestController {
@AutowiredprivateBookRestServicebookRestService;
@GetMapping(path = "/rest/test", produces = MediaType.APPLICATION_JSON_VALUE)
publicBookgetRestBooks() {
returnbookRestService.getRestBook();
}
}

getRestBook() 메서드의 반환값은 Book 객체이지만 @RestController로 설정되어 있으면 JSON 형식의 String형으로 반환된다.

4.2. REST 테스트용 BookRestService 생성

@ServicepublicclassBookRestService {
privatefinalRestTemplaterestTemplate;
publicBookRestService(RestTemplateBuilderrestTemplateBuilder) {
this.restTemplate = restTemplateBuilder.rootUri("/rest/test").build();
}
publicBookgetRestBook() {
returnthis.restTemplate.getForObject("/rest/test", Book.class);
}
}

4.3. @RestClientTest 를 사용한 REST 테스트 코드

@RunWith(SpringRunner.class)
@RestClientTest(BookRestService.class)
publicclassBookRestTest {
@RulepublicExpectedExceptionthrown = ExpectedException.none();
@AutowiredprivateBookRestServicebookRestService;
@AutowiredprivateMockRestServiceServerserver;
@Testpublicvoidrest_test() {
this.server.expect(requestTo("/rest/test"))
.andRespond(withSuccess(newClassPathResource("/test.json", getClass())
, MediaType.APPLICATION_JSON));
Bookbook = this.bookRestService.getRestBook();
assertThat(book.getTitle()).isEqualTo("테스트");
}
@Testpublicvoidrest_error_test() {
this.server.expect(requestTo("/rest/test"))
.andRespond(withServerError());
this.thrown.expect(HttpServerErrorException.class);
this.bookRestService.getRestBook();
}
}

4.4. 테스트 코드에 필요한 리소스 파일

{"idx":null,"title":"테스트","publishedAt":null}

5. @JsonTest

JSON의 직렬화(Serialization)와 역직렬화(Deserialization)를 수행하는 라이브러리인 Gson과 Jackson API의 테스트를 제공

JSON 테스트는 두 가지로 나뉜다. 문자열로 나열된 JSON 데이터를 객체로 변환하여 변환된 객체값을 테스트하거나 그 반대.

5.1. 예시

@RunWith(SpringRunner.class)
@JsonTestpublicclassBookJsonTest {
@AutowiredprivateJacksonTester<Book> json;
@Testpublicvoidjson_test() throwsException {
Bookbook = Book.builder()
.title("테스트")
.build();
Stringcontent = "{\"title\":\"테스트\"}";
assertThat(this.json.parseObject(content).getTitle()).isEqualTo(book.getTitle());
assertThat(this.json.parseObject(content).getPublishedAt()).isNull();
assertThat(this.json.write(book)).isEqualToJson("/test.json");
assertThat(this.json.write(book)).hasJsonPathStringValue("title");
assertThat(this.json.write(book)).extractingJsonPathStringValue("title").isEqualTo("테스트");
}
}

*Reference

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Latest commit

History

History
393 lines (310 loc) · 12.4 KB

File metadata and controls

393 lines (310 loc) · 12.4 KB

Spring Boot Test 정리

1. @SpringBootTest

통합 테스트를 제공

  • 실제 구동되는 어플리케이션과 똑같이 ApplicationContext를 로드하여 테스트
    • 하고 싶은 테스트를 모두 수행 가능
    • 어플리케이션에 설정된 bean을 모두 로드하기 때문에 규모가 클수록 느리다. (단위 테스트가 무의미해짐)
  • 어플리케이션이 실행될 때의 설정을 임의로 바꾸어 테스트 진행 가능
  • 여러 단위 테스트를 하나의 통합된 테스트로 수행할 때 적합
  • 메인클래스와 함께 기본 제공
@RunWith(SpringRunner.class)
@SpringBootTestpublicclassDemoApplicationTests {
@TestpublicvoidcontextLoads() {
}
}

1.1 @RunWith

  • JUnit에 내장된 Runner를 사용하는 대신 어노테이션에 정의된 Runner 클래스 사용
  • @SpringBootTest 를 사용하려면 JUnit 실행에 필요한 SpringJUnit4ClassRunner 클래스를 상속받은 @RunWith(SpringRunner.class)를 붙여야 한다.

1.2. @SpringBootTest 의 파라미터들

value : 테스트가 실행되기 전에 적용할 프로퍼티 주입.(기존의 프로퍼티 오버라이드)
properties : 테스트가 실행되기 전에 {key=value} 형식으로 프로퍼티 추가.
classes : ApplicationContext에 로드할 클래스 지정. (지정하지 않으면 @SpringBootConfiguration을 찾아서 로드)
webEnvironment : 어플리케이션이 실행될 때의 웹 환경을 설정. (기본값은 Mock 서블릿을 로드하여 구동)

// value와 properties는 함께 사용할 수 없으므로 에러 발생@RunWith(SpringRunner.class)
@SpringBootTest(value = "value=test"
, properties = {"property.value=propertyTest"}
, classes = {DemoApplicationTests.class}
, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
publicclassDemoApplicationTests {
@Value("${value}")
privateStringvalue;
@Value("${property.value}")
privateStringpropertyValue;
@TestpublicvoidcontextLoads() {
assertThat(value, is("test"));
assertThat(propertyValue, is("propertyTest"));
}
}

1.3. 사용 팁

  • 프로파일(개발, QA, 운영) 마다 다른 DataSource를 갖는 경우, @ActiveProfiles("local") 을 사용
  • @Transactional을 사용하면 테스트를 마치고 나서 수정된 데이터가 롤백된다.
  • @SpringBootTest는 기본적으로 @SpringBootApplication 이나 @SpringBootConfiguration 을 찾는다. (둘 중 하나는 필수)

2. @WebMvcTest

MVC를 위한 테스트

  • 웹에서 테스트하기 힘든 Controller를 테스트하는데 적합
  • 웹상에서 요청과 응답에 대해 테스트할 수 있다.
  • Security와 Filter까지 자동으로 테스트하며, 수동으로 추가/삭제 가능
  • @WebMvcTest 를 사용하면 MVC 관련 설정들만 로드되기 때문에 가볍게 테스트 가능
    • MVC 관련 설정: @Controller, @ControllerAdvice, @JsonComponent, Filter, WebMvcConfigurer, HandlerMethodArgumentResolver

2.1. 예시

BookController에서 책 리스트를 받아오는 테스트

2.1.1.Book 클래스 생성

@NoArgsConstructor@GetterpublicclassBook {
privateIntegeridx;
privateStringtitle;
privateLocalDateTimepublishedAt;
@BuilderpublicBook(Stringtitle, LocalDateTimepublishedAt) {
this.title = title;
this.publishedAt = publishedAt;
}
}

2.1.2. BookController 클래스 생성

@ControllerpublicclassBookController {
@AutowiredprivateBookServicebookService;
@GetMapping("/books")
publicStringgetBookList(Modelmodel) {
model.addAttribute("bookList", bookService.getBookList());
return"book";
}
}

2.1.3. BookService 인터페이스 생성

이 인터페이스를 구현하는 구현체는 만들지 않고, Mock 데이터를 이용하여 테스트 진행

publicinterfaceBookService {
List<Book> getBookList();
}

2.1.4. BookControllerTest 생성

@RunWith(SpringRunner.class)
@WebMvcTest(BookController.class)
publicclassBookControllerTest {
@AutowiredprivateMockMvcmvc;
@MockBeanprivateBookServicebookService;
@TestpublicvoidBook_MVC_TEST() throwsException {
Bookbook = newBook("Spring Boot Book", LocalDateTime.now());
given(bookService.getBookList()).willReturn(Collections.singletonList(book));
mvc.perform(get("/books"))
.andExpect(status().isOk()) // HTTP 상태값이 200인지 테스트
.andExpect(view().name("book")) // 반환되는 view의 이름이 book인지 테스트
.andExpect(model().attributeExists("bookList")) // Model의 프로퍼티 중 bookList라는 프로퍼티가 존재하는지 테스트
.andExpect(model().attribute("bookList", contains(book))); // Model의 프로퍼티 중 bookList 프로퍼티에 book 객체가 담겨져 있는지 테스트
}
}
  • @WebMvcTest를 사용하기 위해서는 테스트할 컨트롤러 이름을 명시해야 한다. (여기서는 BookContoller)
  • MockMvc는 모든 의존성을 로드하지 않고 BookContoller와 관련된 bean만 로드한다.
    • 여기서는 MockMvc를 주입시켰기 때문에 전체 HTTP서버를 실행하지 않고 테스트 가능
  • BookService를 구현한 구현체는 없지만 @MockBean으로 BookService를 가짜객체로 대체함
  • @DataJpaTest 는 JPA 테스트가 끝날 때 마다 자동으로 사용된 데이터를 롤백
  • EntityManager의 대체재로 만들어진 테스트용 TestEntityManager를 사용하여 persist, flush, find 등의 기본적인 JPA 테스트 가능

3. @DataJpaTest

JPA 관련 테스트 설정만 로드

  • DataSource의 설정이 정상적인지 테스트
  • JPA를 사용하여 데이터를 제대로 생성, 수정, 삭제하는지 테스트
  • 인메모리 임베디드 데이터베이스 사용
    • 메인 메모리를 데이터 저장소로 하여 DB를 어플리케이션에 내장하여 운용하는 DB
  • @Entity 클래스를 스캔하여 Spring Data JPA Repositories를 구성

3.1. 별도의 DataSource 사용하기

기본 설정된 DataSource를 사용하지 않도록 아래와 같이 설정

@RunWith(SpringRunner.class)
@DataJpaTest@ActiveProfiles("...")
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
publicclassJpaTest {
...
}

또는 application.yml 파일을 아래와 같이 수정

spring.test.database.replace: NONE

3.2. 테스트 데이터베이스 선택

3.2.1. 프로퍼티 설정

spring.test.database.connection: H2

3.2.2. 어노테이션 설정

@AutoConfigureTestDatabase(connection = H2)
...

3.3. 예시

3.3.1. Book 클래스에 JPA 관련 어노테이션 추가

@NoArgsConstructor@Getter@Entity@TablepublicclassBook {
@Id@GeneratedValueprivateIntegeridx;
@ColumnprivateStringtitle;
@ColumnprivateLocalDateTimepublishedAt;
@BuilderpublicBook(Stringtitle, LocalDateTimepublishedAt) {
this.title = title;
this.publishedAt = publishedAt;
}
}

3.3.2. BookRepository 생성

publicinterfaceBookRepositoryextendsJpaRepository<Book, Integer> {
}

3.3.3. @DataJpaTest로 테스트 수행하기

@RunWith(SpringRunner.class)
@DataJpaTestpublicclassBookJpaTest {
privatefinalstaticStringBOOT_TEST_TITLE = "Spring Boot Test Book";
@AutowiredprivateTestEntityManagertestEntityManager;
@AutowiredprivateBookRepositorybookRepository;
@TestpublicvoidBookList_Save_Test() {
Bookbook = Book.builder().title(BOOT_TEST_TITLE)
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book);
assertThat(bookRepository.getOne(book.getIdx()), is(book));
}
@TestpublicvoidBookList_Save_Search_Test() {
Bookbook1 = Book.builder().title(BOOT_TEST_TITLE + "1")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book1);
Bookbook2 = Book.builder().title(BOOT_TEST_TITLE + "2")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book2);
Bookbook3 = Book.builder().title(BOOT_TEST_TITLE + "3")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book3);
List<Book> bookList = bookRepository.findAll();
assertThat(bookList, hasSize(3));
assertThat(bookList, contains(book1, book2, book3));
}
@TestpublicvoidBookList_Save_Delete_Test() {
Bookbook1 = Book.builder().title(BOOT_TEST_TITLE + "1")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book1);
Bookbook2 = Book.builder().title(BOOT_TEST_TITLE + "2")
.publishedAt(LocalDateTime.now()).build();
testEntityManager.persist(book2);
bookRepository.deleteAll();
assertThat(bookRepository.findAll(), IsEmptyCollection.empty());
}
}

4. @RestClientTest

REST 통신의 데이터형으로 사용되는 JSON 형식이 예상대로 응답을 반환하는지 등을 테스트

4.1. REST 테스트를 위한 BookRestController

@RestControllerpublicclassBookRestController {
@AutowiredprivateBookRestServicebookRestService;
@GetMapping(path = "/rest/test", produces = MediaType.APPLICATION_JSON_VALUE)
publicBookgetRestBooks() {
returnbookRestService.getRestBook();
}
}

getRestBook() 메서드의 반환값은 Book 객체이지만 @RestController로 설정되어 있으면 JSON 형식의 String형으로 반환된다.

4.2. REST 테스트용 BookRestService 생성

@ServicepublicclassBookRestService {
privatefinalRestTemplaterestTemplate;
publicBookRestService(RestTemplateBuilderrestTemplateBuilder) {
this.restTemplate = restTemplateBuilder.rootUri("/rest/test").build();
}
publicBookgetRestBook() {
returnthis.restTemplate.getForObject("/rest/test", Book.class);
}
}

4.3. @RestClientTest 를 사용한 REST 테스트 코드

@RunWith(SpringRunner.class)
@RestClientTest(BookRestService.class)
publicclassBookRestTest {
@RulepublicExpectedExceptionthrown = ExpectedException.none();
@AutowiredprivateBookRestServicebookRestService;
@AutowiredprivateMockRestServiceServerserver;
@Testpublicvoidrest_test() {
this.server.expect(requestTo("/rest/test"))
.andRespond(withSuccess(newClassPathResource("/test.json", getClass())
, MediaType.APPLICATION_JSON));
Bookbook = this.bookRestService.getRestBook();
assertThat(book.getTitle()).isEqualTo("테스트");
}
@Testpublicvoidrest_error_test() {
this.server.expect(requestTo("/rest/test"))
.andRespond(withServerError());
this.thrown.expect(HttpServerErrorException.class);
this.bookRestService.getRestBook();
}
}

4.4. 테스트 코드에 필요한 리소스 파일

{"idx":null,"title":"테스트","publishedAt":null}

5. @JsonTest

JSON의 직렬화(Serialization)와 역직렬화(Deserialization)를 수행하는 라이브러리인 Gson과 Jackson API의 테스트를 제공

JSON 테스트는 두 가지로 나뉜다. 문자열로 나열된 JSON 데이터를 객체로 변환하여 변환된 객체값을 테스트하거나 그 반대.

5.1. 예시

@RunWith(SpringRunner.class)
@JsonTestpublicclassBookJsonTest {
@AutowiredprivateJacksonTester<Book> json;
@Testpublicvoidjson_test() throwsException {
Bookbook = Book.builder()
.title("테스트")
.build();
Stringcontent = "{\"title\":\"테스트\"}";
assertThat(this.json.parseObject(content).getTitle()).isEqualTo(book.getTitle());
assertThat(this.json.parseObject(content).getPublishedAt()).isNull();
assertThat(this.json.write(book)).isEqualToJson("/test.json");
assertThat(this.json.write(book)).hasJsonPathStringValue("title");
assertThat(this.json.write(book)).extractingJsonPathStringValue("title").isEqualTo("테스트");
}
}

*Reference