← Назад к вопросам

Какие знаешь способы замокать 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");
    }
}

Как это работает:

  1. Spring видит @MockBean
  2. Создаёт Mockito Mock
  3. Регистрирует его в контексте вместо реального Bean
  4. @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 - мокируй только внешние зависимости