Spring BootMiddleLive coding
Как писать unit-тесты и интеграционные тесты в Spring Boot?
Unit-тесты — JUnit 5 + MockitoExtension без контекста; @WebMvcTest для контроллеров с MockMvc; @DataJpaTest для репозиториев; @SpringBootTest + Testcontainers для полного интеграционного теста с реальной PostgreSQL.
Unit-тесты (без контекста Spring)
Для чистых unit-тестов используйте JUnit 5 + Mockito без загрузки Spring-контекста:
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock OrderRepository orderRepo;
@Mock PaymentClient paymentClient;
@InjectMocks OrderService orderService;
@Test
void createOrder_savesAndReturnsId() {
var req = new OrderRequest("item-1", 2);
var saved = new Order(UUID.randomUUID(), "item-1", 2);
when(orderRepo.save(any())).thenReturn(saved);
UUID id = orderService.createOrder(req);
assertThat(id).isEqualTo(saved.getId());
verify(paymentClient, never()).charge(any()); // не вызывается при создании
}
}
Тест слоя контроллера (@WebMvcTest)
Загружает только MVC-слой, без БД и сервисов:
@WebMvcTest(OrderController.class)
class OrderControllerTest {
@Autowired MockMvc mvc;
@MockBean OrderService orderService;
@Test
void getOrder_returns200() throws Exception {
var id = UUID.randomUUID();
when(orderService.findById(id))
.thenReturn(new OrderDto(id, "item-1", 2));
mvc.perform(get("/api/orders/{id}", id)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.item").value("item-1"));
}
}
Тест слоя репозитория (@DataJpaTest)
Поднимает только JPA-контекст с in-memory H2 (или Testcontainers):
@DataJpaTest
class OrderRepositoryTest {
@Autowired OrderRepository repo;
@Autowired TestEntityManager em;
@Test
void findByStatus_returnsMatchingOrders() {
em.persistAndFlush(new Order(null, "item-1", 1, OrderStatus.PENDING));
em.persistAndFlush(new Order(null, "item-2", 3, OrderStatus.DONE));
List<Order> pending = repo.findByStatus(OrderStatus.PENDING);
assertThat(pending).hasSize(1);
assertThat(pending.get(0).getItem()).isEqualTo("item-1");
}
}
Интеграционные тесты (@SpringBootTest)
Поднимает полный контекст. Для реальной БД используйте Testcontainers:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
class OrderIntegrationTest {
@Container
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:17")
.withDatabaseName("testdb");
@DynamicPropertySource
static void props(DynamicPropertyRegistry r) {
r.add("spring.datasource.url", postgres::getJdbcUrl);
r.add("spring.datasource.username", postgres::getUsername);
r.add("spring.datasource.password", postgres::getPassword);
}
@Autowired TestRestTemplate restTemplate;
@Test
void createAndFetchOrder() {
var req = new OrderRequest("item-1", 2);
var resp = restTemplate.postForEntity("/api/orders", req, OrderDto.class);
assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
var fetched = restTemplate.getForObject(
"/api/orders/{id}", OrderDto.class, resp.getBody().getId());
assertThat(fetched.getItem()).isEqualTo("item-1");
}
}
Подводные камни
@SpringBootTestподнимает весь контекст — медленно; используйте его только для интеграционных тестов.@MockBeanв@WebMvcTestпересоздаёт Spring-контекст при каждом уникальном наборе — замедляет билд; группируйте тесты.@DataJpaTestпо умолчанию откатывает каждый тест (@Transactional) — данные не загрязняют друг друга, но side effects могут быть неочевидны.- H2 не полностью совместим с PostgreSQL-диалектом (JSONB, array-типы) — предпочитайте Testcontainers.
- Testcontainers требуют работающий Docker-демон в CI; убедитесь, что агент имеет доступ к Docker socket.
@DynamicPropertySourceдолжен быть static; ошибка при несоблюдении приводит к NPE при старте.- Не проверяйте в unit-тестах поведение Mockito-заглушек — тестируйте поведение кода, а не конфигурацию моков.
Common mistakes
- Путать термин «unit integration tests» с соседним механизмом Spring Boot.
- Не называть границу lifecycle, transaction, thread или request для «unit integration tests».
- Игнорировать production-эффекты «unit integration tests»: latency, SQL shape, memory, security или observability.
What the interviewer is testing
- Попросить объяснить механизм «unit integration tests» на минимальном примере.
- Проверить, видит ли кандидат failure mode и диагностику для «unit integration tests».
- Уточнить, какие настройки или API меняют «unit integration tests» в реальном сервисе.