Spring Boot Test Cheat Sheet
๐ Spring Boot Test Cheat Sheet ์ ๋๋ค. ์ค๋ช ์ ์๊ณ ์์ฃผ ์ฌ์ฉํ๋ ํ ์คํธ ์ฝ๋ ์ฐธ๊ณ ์ฉ ์ ๋๋ค.
build.gradle
dependencies {
runtimeOnly 'com.h2database:h2'
implementation 'com.google.guava:guava:31.1-jre'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.rest-assured:rest-assured'
}
application-test.yml
spring:
datasource:
url: jdbc:h2:mem:test
username: sa
password:
driver-class-name: org.h2.Driver
jpa:
hibernate:
ddl-auto: 'create-drop'
import
import static org.hamcrest.Matchers;
import static org.junit.jupiter.api.Assertions.*;
import static org.assertj.core.api.Assertions.*;
DatabaseCleanup
JPA์ฉ, ์ ํ๋ฆฌ์ผ์ด์ ๋ด ๋ชจ๋ ์ํฐํฐ ํ ์ด๋ธ ์ด๊ธฐํ ํด๋์ค
@Service
@ActiveProfiles("test")
public class DatabaseCleanup implements InitializingBean {
@PersistenceContext
private EntityManager entityManager;
private List<String> tableNames;
@Override
public void afterPropertiesSet() {
tableNames = entityManager.getMetamodel().getEntities().stream()
.filter(e -> e.getJavaType().getAnnotation(Entity.class) != null)
.map(e -> CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, e.getName()))
.collect(Collectors.toList());
}
@Transactional
public void execute() {
entityManager.flush();
entityManager.createNativeQuery("SET REFERENTIAL_INTEGRITY FALSE").executeUpdate();
for (String tableName : tableNames) {
entityManager.createNativeQuery("TRUNCATE TABLE " + tableName).executeUpdate();
entityManager.createNativeQuery("ALTER TABLE " + tableName + " ALTER COLUMN ID RESTART WITH 1").executeUpdate();
}
entityManager.createNativeQuery("SET REFERENTIAL_INTEGRITY TRUE").executeUpdate();
}
}
Acceptance Abstract
RestAssured ์ฌ์ฉ์ ๊ณตํต ๋ถ๋ชจ ํด๋์ค. ๋๋คํฌํธ์ ํ ์ด๋ธ ์ด๊ธฐํ
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public abstract class AcceptanceTest {
@LocalServerPort
int port;
@Autowired
private DatabaseCleanup databaseCleanup;
@BeforeEach
public void setUp() {
RestAssured.port = port;
databaseCleanup.execute();
}
}
RestAssured
public static ExtractableResponse<Response> ์๋์ฐจ_๋ฑ๋ก_์์ฒญ(String name) {
Map<String, Object> params = new HashMap<>();
params.put("name", name);
return RestAssured
.given().log().all()
.body(params)
.contentType(MediaType.APPLICATION_JSON_VALUE)
.when().post("/api/cars")
.then().log().all()
.extract();
}
public static void ์๋์ฐจ_๋ฑ๋ก๋จ(ExtractableResponse<Response> response) {
assertThat(response.statusCode()).isEqualTo(HttpStatus.CREATED.value());
assertThat(response.header("Location")).isNotBlank();
}
public static ExtractableResponse<Response> ์ ๋ณด_์กฐํ_์์ฒญ(String accessToken) {
return RestAssured
.given().log().all()
.auth().oauth2(accessToken)
.when().get("/members/me")
.then().log().all()
.extract();
}
@ExtendWith(SpringExtension.class), @MockBean
@ExtendWith(SpringExtension.class)
public class SpringExtensionTest {
@MockBean
private LineRepository lineRepository;
@MockBean
private StationService stationService;
@Test
void findAllLines() {
// given
when(lineRepository.findAll()).thenReturn(Lists.newArrayList(new Line()));
LineService lineService = new LineService(lineRepository, stationService);
// when
List<LineResponse> responses = lineService.findLineResponses();
// then
assertThat(responses).hasSize(1);
}
}
@ExtendWith(MockitoExtension.class), @Mock, @InjectMocks
@ExtendWith(MockitoExtension.class)
public class MockitoExtensionTest {
@Mock
private CarRepository carRepository;
@InjectMocks
private CarService carService;
@DisplayName("์ด๋ฆ์ ๋ฐ์ ์๋์ฐจ๋ฅผ ์ ์ฅํ๋ค.")
@Test
public void save(){
// given
final String NAME = "Boong-Boong";
when(carRepository.save(any())).thenReturn(Optional.of(new Car(NAME)));
// when
CarResponse carResponse = carService.save(NAME);
// then
assertThat(carResponse.getName()).isEqualTo(NAME);
}
}
@WebMvcTest, @MockBean, MockMvc
@WebMvcTest(CarController.class)
public class CarControllerTest {
private static final String API_BASE_PATH = "/api/cars";
@Autowired
private MockMvc mockMvc;
@MockBean
private CarService carService;
@Autowired
private ObjectMapper objectMapper;
@DisplayName("์๋์ฐจ๋ฅผ ์์ฑํ๋ค.")
@Test
public void createCar() throws Exception {
//given
CarRequest carRequest = new carRequest("Boong-Boong");
CarResponse carResponse = new CarResponse(1L, carRequest.getName());
given(carService.save(any())).willReturn(carResponse);
//when, then
mockMvc.perform(post(API_BASE_PATH)
.content(objectMapper.writeValueAsString(carRequest))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.id").value(carResponse.getId()));
}
}
@SpringBootTest, @AutoConfigureMockMvc, MockMvc
@SpringBootTest
@AutoConfigureMockMvc
public class CarControllerMockTest {
private static final String API_BASE_PATH = "/api/cars";
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@DisplayName("์๋์ฐจ๋ฅผ ์์ฑํ๋ค.")
@Test
public void createCar() throws Exception {
//given
CarRequest carRequest = new carRequest("Boong-Boong");
CarResponse carResponse = new CarResponse(1L, carRequest.getName());
//when, then
mockMvc.perform(post(API_BASE_PATH)
.content(objectMapper.writeValueAsString(carRequest))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.id").value(carResponse.getId()));
}
}
@DataJpaTest, @PersistenceContext
- JPA ๊ด๋ จ ์ค์ ๋ง ๋ก๋
- ๋ฐ์ดํฐ ์์ค, ์ํฐํฐ ๋งค๋์ ๋ฑ ์์ฑ
- @Entity ์ด๋ ธํ ์ด์ ์ด ๋ถ์ ํด๋์ค ๋ฐ Spring Data JPA ์ ๋ํ ์ค์ ๋ค ์ ์ฉ
- ๊ธฐ๋ณธ์ผ๋ก in-memory database ์ฌ์ฉ
- @AutoConfigureTestDatabase๋ฅผ ์ฌ์ฉํ์ฌ ์ด๋ค database๋ฅผ ์ฐ๊ฒฐํ ์ง ์ ํํ ์ ์๋ค.
@DataJpaTest
@ActiveProfiles("test")
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class CarRepositoryTest {
@Autowired
private CarRepository carRepository;
@PersistenceContext
private EntityManager entityManager;
@Test
@DisplayName("์๋์ฐจ๋ฅผ ์ ์ฅํ๋ค.")
public void save() {
...
Car savedCar = carRepository.save(new Car("Boong-Boong"));
entityManager.flush();
entityManager.clear();
...
}
}
@MybatisTest
- Mybatis ํ ์คํธ ๊ฐ์ด๋
- ๊ธฐ๋ณธ์ผ๋ก in-memory database ์ฌ์ฉ
- @AutoConfigureTestDatabase๋ฅผ ์ฌ์ฉํ์ฌ ์ด๋ค database๋ฅผ ์ฐ๊ฒฐํ ์ง ์ ํํ ์ ์๋ค.
@ExtendWith(SpringExtension.class)
@MybatisTest
@ActiveProfiles("test")
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class OrderTest {
@Autowired
private OrderMapper orderMapper;
@Test
public void mybatis_test() throws Exception {
// given
String seq = "1";
// when
OrderVO vo = orderMapper.getOrder(seq);
// then
assertThat(vo.getSeq).isEqualTo("1");
}
}
@Mock, @MockBean ์ฐจ์ด์
Mock ์ข ๋ฅ | ์์กด์ฑ ์ฃผ์ ๋์ |
---|---|
@Mock | @InjectMocks |
@MockBean | Spring Context |
@Mock
์@InjectMocks
์ ๋ํด์๋ง ํด๋น ํด๋์ค์์์ ์ ์๋ ๊ฐ์ฒด๋ฅผ ์ฐพ์์ ์์กด์ฑ์ ํด๊ฒฐ@MockBean
์mock
๊ฐ์ฒด๋ฅผ ์คํ๋ง ์ปจํ ์คํธ์ ๋ฑ๋ก
MockMVC ์ฃผ์ ๋ฐฉ๋ฒ
@SpringBootTest
+@AutoConfigureMockMvc
: MockMVC๋ฅผ ์ด์ฉํ์ฌ ํตํฉํ ์คํธ ํ ๋ ์ฌ์ฉ@WebMvcTest
: MVC๋ง ์ฌ๋ผ์ด์ค(slice) ํ ์คํธ ํ ๋ ์ฌ์ฉ
๋๊ธ๋จ๊ธฐ๊ธฐ