스프링 부트 테스트 (1)

스프링부트 테스트

스프링부트2에서는 기본적인 테스트 스타터를 제공한다. 기본적으로 제공되는 테스트 어노테이션은 아래와 같이 제공됩니다.

  1. @SpringBootTest
  2. @WebMvcTest
  3. @DataJpaTest
  4. @RestClientTest
  5. @JsonTest
  6. @DataRedisTest

이 글에서 @SpringBootTest, @WebMvcTest, @DataJpaTest에 관해서 살펴보도록 하겠습니다.


@SpringBootTest

통합 테스트를 제공하는 기본적인 부트 테스트 어노테이션입니다. 여러단위의 테스트들을 하나의 테스트로 수행할 때 적합 합니다.
하지만, 애플리케이션이 실행되는 것과 동일하게 애플리케이션에 설정된 모든 빈들을 로드하기 때문에 애플리케이션 규모가 클수록 느려집니다.

  • @ActiveProfiles("local")과 같은 방식으로 프로파일 환경값을 부여할 수 있다.
  • 테스트에서 @Transactional을 사용하면 데이터가 롤백됩니다. 하지만, 서버의 다른 스레드에서 실행 중이면 WebEnvironment의 RANDOM_PORTDEFINDE_PORT를 사용해서 테스트를 수행해도 트랜잭션이 롤백되지 않습니다.
  • @SpringBootTest는 기본적으로 @SpringBootApplication이나 @SpringBootConfiguration 어노테이션을 찾습니다. 둘 중 하나는 필수이다.

@WebMvcTest

MVC관련 설정인 @Controller, @ControllerAdvice, @JsonComponent, Filter,WebMvcConfigurer,HandlerMethodArgumentResolver만 로드되므로 @SpringBootTest보다 가볍게 테스트 가능합니다.

특징

  • 웹에서 테스트하기 힘든 컨트롤러를 테스트하는데 적합합니다.
  • 웹상에서 요청응답에 대해 테스트할 수 있습니다.
  • 시큐리티 혹은 필터까지 자동으로 테스트하며 수동으로 추가/삭제까지 가능합니다.

WebMvcTest 예시

1.Book

1
2
3
4
5
6
7
8
9
10
11
12
13
@Getter
@NoArgsConstructor
public class Book {
private Integer idx;
private String title;
private LocalDateTime publishedAt;

@Builder
public Book(String title, LocalDateTime publishedAt) {
this.title = title;
this.publishedAt = publishedAt;
}
}

2.BookController

1
2
3
4
5
6
7
8
9
10
11
@Controller
@RequiredArgsConstructor
public class BookController {
private final BookService bookService;

@GetMapping("/books")
public String getBookList(Model model) {
model.addAttribute("bookList", bookService.getBookList());
return "book";
}
}

3.BookService

1
2
3
public interface BookService {
List<Book> getBookList();
}
  • Book타입의 리스트를 넘기는 메서드를 작성하고, 실제 구현체는 만들지않고, mock데이터를 이용해 테스트를 진행하겠습니다.

4.BookControllerTest

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import java.time.LocalDateTime;
import java.util.Collections;

import static org.hamcrest.Matchers.contains;
import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@RunWith(SpringRunner.class)
@WebMvcTest(BookCotroller.class)
public class BookControllerTest {

@Autowired
private MockMvc mvc;

@MockBean
private BookService bookService;

@Test
public void Book_MVC_테스트() throws Exception{
Book book = new Book("Spring Boot Book", LocalDateTime.now());
given(bookService.getBookList()).willReturn(Collections.singletonList(book));

mvc.perform(get("/book"))
.andExpect(status().isOk())
.andExpect(view().name("book"))
.andExpect(model().attributeExists("bookList"))
.andExpect(model().attribute("bookList", contains(book)));
}

}
  • @WebMvcTest를 사용하기 위해 테스트할 특정 컨트롤러명을 명시해주어야합니다. 주입된 MockMvc는 컨트롤러 테스트 시 모든 의존성을 로드하지않고, BookController관련된 빈만 로드하여 가볍게 테스트를 수행합니다.
  • @Service어노테이션은 WebMvcTest의 적용대상이 아닙니다. BookService 인터페이스를 구현한 구현체가 없지만 @MockBean을 적극활용하여 컨트롤러 내부의 의존성 요소인 BookService를 가짜 객체로 대체하였습니다. 이러한 것을 목 객체라고 하고, 실제 객체는 아니지만 특정행위(given())를 지정하여 실제 객체처럼 동작하게 할 수 있습니다.

@DataJpaTest

이 어노테이션은 JPA 관련 테스트 설정만 로드합니다. 데이터소스의 설정이 정상적인지, JPA를 사용하여 데이터를 제대로 CRUD하는지 테스트 할 수 있습니다.

특징

  • 기본적으로 인메모리 임베디드 데이터베이스를 사용하며, @Entity클래스를 스캔하여 JPA저장소를 구성합니다.
  • JPA테스트가 끝날 때마다 자동으로 데이터를 롤백합니다.
  • @AutoConfigureTestDatabase 어노테이션의 기본 설정값인 Replace.Any를 사용하면 기본 내장 데이터소스를 사용합니다. Replace.NONE으로 설정하면 프로파일에 설정한 환경 값에 따라 적용됩니다.

@DataJpaTest 예시

1.Book

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Getter
@NoArgsConstructor
@Entity
@Table
public class Book {
@Id
@GeneratedValue
private Integer idx;

@Column
private String title;

@Column
private LocalDateTime publishedAt;

@Builder
public Book(String title, LocalDateTime publishedAt) {
this.title = title;
this.publishedAt = publishedAt;
}
}

2.BookRepository

1
2
3
public interface BookRepository extends JpaRepository<Book, Integer> {

}

3.BookJpaTest

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import org.hamcrest.collection.IsEmptyCollection;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.test.context.junit4.SpringRunner;

import java.time.LocalDateTime;
import java.util.List;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.core.Is.is;
@RunWith(SpringRunner.class)
@DataJpaTest
public class BookJpaTest {
private final static String BOOT_TEST_TITLE = "SPRING Boot Test Book";

@Autowired
private TestEntityManager testEntityManager;

@Autowired
private BookRepository bookRepository;

@Test
public void Book_저장하기_테스트() {
Book book = Book.builder
.title(BOOT_TEST_TITLE)
.publishedAt(LocalDateTime.now())
.build();

testEntityManager.persist(book);
assertThat(bookRepository.getOne(book.getIdx()), is(book));
}

@Test
public void BookList_저장하고_검색_테스트() {
Book book1 = Book.builder
.title(BOOT_TEST_TITLE +"1")
.publishedAt(LocalDateTime.now())
.build();
testEntityManager.persist(book1);

Book book2 = Book.builder
.title(BOOT_TEST_TITLE +"2")
.publishedAt(LocalDateTime.now())
.build();
testEntityManager.persist(book2);

Book book3 = 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));
}

@Test
public void BookList_저장하고_삭제_테스트() {
Book book1 = Book.builder
.title(BOOT_TEST_TITLE +"1")
.publishedAt(LocalDateTime.now())
.build();
testEntityManager.persist(book1);

Book book2 = Book.builder
.title(BOOT_TEST_TITLE +"2")
.publishedAt(LocalDateTime.now())
.build();
testEntityManager.persist(book2);

bookRepository.deleteAll();
assertThat(bookRepository.findAll(), IsEmptyCollection.empty());
}
}

Reference


0%