Testcontainers로 Spring Boot REST API 테스트하기
Testcontainers로 Spring Boot REST API 테스트하기
이 가이드에서는 Spring Data JPA와 PostgreSQL을 사용하는 Spring Boot REST API를 만들고, Testcontainers와 REST Assured로 테스트하는 방법을 배워요.
출처: 문서
본문
이 가이드를 통해 다음 내용을 배울 수 있어요.
- REST API 엔드포인트를 가진 Spring Boot 애플리케이션 만들기
- 데이터 저장과 조회에 Spring Data JPA와 PostgreSQL 사용하기
- Testcontainers와 REST Assured로 REST API 테스트하기
사전 준비 (Prerequisites)
- Java 17 이상
- Maven 또는 Gradle
- Testcontainers가 지원하는 Docker 환경
참고: Testcontainers가 처음이라면 Testcontainers 개요를 방문해 알아보는 걸 권장해요.
Spring Boot 프로젝트 만들기
Spring Initializr에서 Spring Web, Spring Data JPA, PostgreSQL Driver, Testcontainers 스타터를 선택해 Spring Boot 프로젝트를 만들어요. 또는 가이드 저장소를 클론해도 돼요. pom.xml의 핵심 의존성은 다음과 같아요.
<properties>
<java.version>17</java.version>
<testcontainers.version>2.0.4</testcontainers.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-postgresql</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
모든 Testcontainers 모듈 의존성에 버전을 반복하지 않도록 Testcontainers BOM(Bill of Materials)을 사용하는 걸 권장해요.
JPA 엔티티 만들기
Customer.java를 만들어요.
package com.testcontainers.demo;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
@Entity
@Table(name = "customers")
class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(nullable = false, unique = true)
private String email;
public Customer() {}
public Customer(Long id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
Spring Data JPA 리포지토리 만들기
package com.testcontainers.demo;
import org.springframework.data.jpa.repository.JpaRepository;
interface CustomerRepository extends JpaRepository<Customer, Long> {}
스키마 생성 스크립트 추가하기
src/main/resources/schema.sql을 만들어요.
create table if not exists customers (
id bigserial not null,
name varchar not null,
email varchar not null,
primary key (id),
UNIQUE (email)
);
src/main/resources/application.properties에서 스키마 초기화를 활성화해요.
spring.sql.init.mode=always
REST API 엔드포인트 만들기
CustomerController.java를 만들어요.
package com.testcontainers.demo;
import java.util.List;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
class CustomerController {
private final CustomerRepository repo;
CustomerController(CustomerRepository repo) {
this.repo = repo;
}
@GetMapping("/api/customers")
List<Customer> getAll() {
return repo.findAll();
}
}
Testcontainers로 테스트 작성하기
REST API를 테스트하려면 실행 중인 Postgres 데이터베이스와 시작된 Spring 컨텍스트가 필요해요. Testcontainers가 Docker 컨테이너에서 Postgres를 띄우고 @DynamicPropertySource가 이를 Spring에 연결해요.
CustomerControllerTest.java를 만들어요.
package com.testcontainers.demo;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.hasSize;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import java.util.List;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.postgresql.PostgreSQLContainer;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class CustomerControllerTest {
@LocalServerPort
private Integer port;
static PostgreSQLContainer postgres = new PostgreSQLContainer("postgres:16-alpine");
@BeforeAll
static void beforeAll() {
postgres.start();
}
@AfterAll
static void afterAll() {
postgres.stop();
}
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
@Autowired
CustomerRepository customerRepository;
@BeforeEach
void setUp() {
RestAssured.baseURI = "http://localhost:" + port;
customerRepository.deleteAll();
}
@Test
void shouldGetAllCustomers() {
List<Customer> customers = List.of(
new Customer(null, "John", "[email protected]"),
new Customer(null, "Dennis", "[email protected]")
);
customerRepository.saveAll(customers);
given()
.contentType(ContentType.JSON)
.when()
.get("/api/customers")
.then()
.statusCode(200)
.body(".", hasSize(2));
}
}
테스트가 하는 일을 살펴보면,
@SpringBootTest는 임의의 포트에서 전체 애플리케이션을 시작해요.PostgreSQLContainer가@BeforeAll에서 시작되고@AfterAll에서 중지돼요.@DynamicPropertySource는 컨테이너의 JDBC URL, 사용자 이름, 비밀번호를 Spring에 등록해서 데이터소스가 테스트 컨테이너에 연결되게 해요.@BeforeEach는 각 테스트 전에 모든 고객 행을 삭제해서 테스트 오염을 막아요.shouldGetAllCustomers()는 고객 두 명을 삽입하고GET /api/customers를 호출해 응답에 레코드 2개가 포함되는지 검증해요.
테스트 실행과 다음 단계
테스트를 실행해요.
$ ./mvnw test
또는 Gradle로,
$ ./gradlew test
Postgres Docker 컨테이너가 시작되고 모든 테스트가 통과하는 걸 볼 수 있어요. 테스트가 끝나면 컨테이너는 자동으로 중지되고 제거돼요.
요약 (Summary)
Testcontainers 라이브러리는 목(mock)이나 인메모리 데이터베이스 대신 프로덕션에서 쓰는 것과 같은 종류의 데이터베이스(Postgres)를 사용해 통합 테스트를 작성할 수 있게 도와줘요. 실제 서비스에 대해 테스트하기 때문에 코드를 리팩터링해도 애플리케이션이 예상대로 동작하는지 검증할 수 있답니다.
Testcontainers에 대해 더 알아보고 싶다면 Testcontainers 개요를 방문해요.
더 읽어보기 (Further reading)
- Testcontainers JUnit 5 빠른 시작
- Testcontainers Postgres 모듈
- Testcontainers JDBC 지원