← Назад к вопросам
Какие знаешь способы замокать Bean в интеграционных тестах?
2.3 Middle🔥 131 комментариев
#Spring Framework#Тестирование
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Способы замокать Bean в интеграционных тестах Spring
В интеграционных тестах часто нужно заменить реальный Bean на Mock версию. Есть несколько проверенных подходов.
1. @MockBean (Рекомендуется)
Аннотация из spring-boot-test, автоматически заменяет Bean в контексте:
@SpringBootTest
class UserServiceIntegrationTest {
@MockBean
private EmailService emailService; // Замокан
@Autowired
private UserService userService; // Реальный
@Test
void testUserCreationSendsEmail() {
// Setup
when(emailService.sendWelcomeEmail(anyString()))
.thenReturn(true);
// Execute
User user = userService.createUser("john@example.com");
// Verify
assertThat(user).isNotNull();
verify(emailService).sendWelcomeEmail("john@example.com");
}
}
Как это работает:
- Spring видит @MockBean
- Создаёт Mockito Mock
- Регистрирует его в контексте вместо реального Bean
- @Autowired получает Mock
Плюсы:
- Простотой
- Работает с @SpringBootTest
- Автоматически очищается между тестами
Минусы:
- Загружает весь контекст (медленнее)
- Не работает с @WebMvcTest/@DataJpaTest из коробки
2. @MockBean с @WebMvcTest
Для тестирования контроллеров:
@WebMvcTest(UserController.class)
class UserControllerTest {
@MockBean
private UserService userService;
@Autowired
private MockMvc mockMvc;
@Test
void testGetUser() throws Exception {
when(userService.getById(1L))
.thenReturn(new User(1L, "John"));
mockMvc.perform(get("/api/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("John"));
verify(userService).getById(1L);
}
}
Разница:
- @SpringBootTest: загружает всё
- @WebMvcTest: только контроллеры + их зависимости
- Сервисы нужно мокировать
3. @SpyBean (Partial Mock)
Замокировать частично, оставить реальную реализацию:
@SpringBootTest
class UserServiceSpyTest {
@SpyBean
private UserService userService; // Реальный, но можно мокировать методы
@MockBean
private UserRepository userRepository;
@Test
void testUserServiceWithRealMethods() {
when(userRepository.save(any()))
.thenReturn(new User(1L, "John"));
// Вызывает реальный userService.createUser()
// Но userRepository замокан
User user = userService.createUser("john@example.com");
// Можем также замокировать отдельный метод
doReturn("ADMIN").when(userService).getUserRole(1L);
String role = userService.getUserRole(1L); // Вернёт "ADMIN"
}
}
@SpyBean vs @MockBean:
- @SpyBean: вызывает реальный метод, может переопределить поведение
- @MockBean: всё замокировано, нужно явно вернуть значение
4. TestConfiguration с @Primary
Для более гибкого контроля контекста:
@SpringBootTest
class UserServiceWithTestConfigTest {
@TestConfiguration
static class TestConfig {
@Bean
@Primary // Этот Bean приоритетнее
public EmailService mockEmailService() {
EmailService mock = Mockito.mock(EmailService.class);
when(mock.sendWelcomeEmail(anyString()))
.thenReturn(true);
return mock;
}
}
@Autowired
private UserService userService;
@Autowired
private EmailService emailService;
@Test
void testWithTestConfiguration() {
userService.createUser("john@example.com");
verify(emailService).sendWelcomeEmail("john@example.com");
}
}
Когда использовать:
- Нужна сложная настройка Mock
- Несколько тестовых конфигураций
- Контроль над жизненным циклом Mock
5. @Bean с Mockito.mock()
Мануальное создание в конфигурации:
@Configuration
public class TestConfig {
@Bean
public ExternalApiClient externalApiClient() {
ExternalApiClient mock = Mockito.mock(ExternalApiClient.class);
when(mock.callApi(anyString()))
.thenReturn(new ApiResponse("success"));
return mock;
}
}
@SpringBootTest
class MyServiceTest {
@Autowired
private ExternalApiClient apiClient;
@Autowired
private MyService myService;
@Test
void test() {
myService.processData();
verify(apiClient).callApi(anyString());
}
}
6. ArgumentCaptor для проверки
Захватить аргументы вызова метода:
@SpringBootTest
class PaymentServiceTest {
@MockBean
private PaymentGateway paymentGateway;
@Autowired
private PaymentService paymentService;
@Test
void capturePaymentDetails() {
ArgumentCaptor<PaymentRequest> captor =
ArgumentCaptor.forClass(PaymentRequest.class);
paymentService.processPayment("123", 99.99);
verify(paymentGateway).charge(captor.capture());
PaymentRequest capturedRequest = captor.getValue();
assertThat(capturedRequest.getAmount()).isEqualTo(99.99);
assertThat(capturedRequest.getOrderId()).isEqualTo("123");
}
}
7. BDDMockito (Given-When-Then стиль)
Для лучшей читаемости:
import static org.mockito.BDDMockito.*;
@SpringBootTest
class BDDStyleTest {
@MockBean
private UserRepository userRepository;
@Autowired
private UserService userService;
@Test
void testWithBDDStyle() {
// Given
User expectedUser = new User(1L, "John");
given(userRepository.findById(1L))
.willReturn(Optional.of(expectedUser));
// When
User result = userService.getUser(1L);
// Then
assertThat(result.getName()).isEqualTo("John");
then(userRepository).should().findById(1L);
}
}
8. Сложный сценарий: Нескольких Mock с разным поведением
@SpringBootTest
class ComplexMockingTest {
@MockBean
private UserRepository userRepository;
@MockBean
private EmailService emailService;
@MockBean
private SmsService smsService;
@Autowired
private UserService userService;
@Test
void testComplexScenario() {
// Setup multiple mocks
given(userRepository.save(any()))
.willReturn(new User(1L, "John"));
given(emailService.sendWelcomeEmail(anyString()))
.willReturn(true);
given(smsService.sendSms(anyString(), anyString()))
.willThrow(new SmsException("Network error"));
// Execute
User user = userService.createUser("john@example.com");
// Verify all interactions
InOrder inOrder = inOrder(userRepository, emailService, smsService);
inOrder.verify(userRepository).save(any());
inOrder.verify(emailService).sendWelcomeEmail("john@example.com");
inOrder.verify(smsService).sendSms("john", anyString());
// Verify no more interactions
verifyNoMoreInteractions(userRepository);
}
}
9. Reset Mock между тестами
@SpringBootTest
class ResetMockTest {
@MockBean
private EmailService emailService;
@BeforeEach
void resetMock() {
Mockito.reset(emailService); // Очищает все вызовы
}
@Test
void testOne() {
// emailService в чистом состоянии
}
@Test
void testTwo() {
// emailService в чистом состоянии
}
}
10. Матрица выбора способа
╔════════════════════════╦═════════════════╦═══════════════════════╦════════════╗
║ Способ ║ Контекст ║ Сложность ║ Скорость ║
╠════════════════════════╬═════════════════╬═══════════════════════╬════════════╣
║ @MockBean ║ @SpringBootTest ║ Низкая ║ Медленно ║
║ @WebMvcTest + @MockBean║ HTTP тесты ║ Низкая ║ Быстро ║
║ @SpyBean ║ Partial Mock ║ Средняя ║ Средне ║
║ TestConfiguration ║ Реалистичнее ║ Средняя ║ Средне ║
║ BDDMockito ║ Читаемость ║ Низкая ║ Медленно ║
║ ArgumentCaptor ║ Проверка Args ║ Средняя ║ Медленно ║
╚════════════════════════╩═════════════════╩═══════════════════════╩════════════╝
Рекомендации
- Начни с @MockBean - простейший способ
- @WebMvcTest для контроллеров - быстрее чем @SpringBootTest
- TestConfiguration для сложных сценариев - более читаемо
- BDDMockito для читаемости - when/then вместо given/should
- Избегай over-mocking - мокируй только внешние зависимости