Spring Boot Test Slices – Szybkie i Skuteczne Testowanie

TL;DR: Spring Boot Test Slices to adnotacje jak @WebMvcTest, @DataJpaTest, które ładują tylko część aplikacji Spring Boot potrzebną do testów. Dzięki temu testy są szybsze (sekundy zamiast minut) i bardziej precyzyjne. Idealne dla testów jednostkowych konkretnych warstw aplikacji.

## Dlaczego Test Slices są ważne?

W tradycyjnym testowaniu Spring Boot całą aplikację startujemy za każdym razem – to może trwać 30-60 sekund. W firmie z setkami testów to oznacza godziny oczekiwania. Test Slices ładują tylko potrzebne komponenty, skracając czas testów z minut do sekund. To bezpośrednio wpływa na produktywność zespołu i częstotliwość uruchamiania testów przez developerów.

Co się nauczysz:

  • Jak używać @WebMvcTest do testowania kontrolerów
  • Testowanie warstwy danych z @DataJpaTest
  • Różnice między Test Slices a @SpringBootTest
  • Praktyczne przykłady z rzeczywistych projektów
  • Kiedy używać którego typu testu i dlaczego
Wymagania wstępne: Podstawy Spring Boot 2.1+, znajomość JUnit 4/5, podstawowe rozumienie testowania jednostkowego w Javie, znajomość Maven/Gradle.

## Czym są Test Slices?

Test Slices to specjalne adnotacje w Spring Boot, które ładują tylko konkretną „warstwę” aplikacji zamiast całego kontekstu. Każda adnotacja konfiguruje Spring Context z tylko niezbędnymi komponentami.

Test Slice – adnotacja która ładuje tylko określoną część aplikacji Spring Boot, np. tylko kontrolery, tylko repozytoria, tylko konfigurację security

Analogia: Wyobraź sobie wielopiętrowy biurowiec. Zamiast oświetlać całe biuro żeby przetestować windę, oświetlasz tylko szyb windowy. Test Slices działają podobnie – „oświetlają” tylko tę część aplikacji, którą testujesz.

## @WebMvcTest – Testowanie Kontrolerów

@WebMvcTest ładuje tylko warstwę web: kontrolery, filtry, konfigurację MVC. Nie ładuje serwisów ani repozytoriów.

### Przykład kontrolera do testowania

@RestController
@RequestMapping("/api/users")
public class UserController {
    
    private final UserService userService;
    
    public UserController(UserService userService) {
        this.userService = userService;
    }
    
    @GetMapping("/{id}")
    public ResponseEntity getUser(@PathVariable Long id) {
        User user = userService.findById(id);
        return ResponseEntity.ok(user);
    }
    
    @PostMapping
    public ResponseEntity createUser(@RequestBody @Valid User user) {
        User created = userService.save(user);
        return ResponseEntity.status(HttpStatus.CREATED).body(created);
    }
}

### Test z @WebMvcTest

@WebMvcTest(UserController.class)
public class UserControllerTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @MockBean // Spring Boot automatycznie mockuje serwis
    private UserService userService;
    
    @Test
    public void shouldReturnUser_WhenUserExists() throws Exception {
        // Given
        User user = new User(1L, "Jan Kowalski", "jan@example.com");
        when(userService.findById(1L)).thenReturn(user);
        
        // When & Then
        mockMvc.perform(get("/api/users/1"))
            .andExpect(status().isOk())
            .andExpected(jsonPath("$.name").value("Jan Kowalski"))
            .andExpected(jsonPath("$.email").value("jan@example.com"));
    }
    
    @Test
    public void shouldCreateUser_WhenValidData() throws Exception {
        // Given
        User user = new User(null, "Anna Nowak", "anna@example.com");
        User savedUser = new User(2L, "Anna Nowak", "anna@example.com");
        when(userService.save(any(User.class))).thenReturn(savedUser);
        
        // When & Then
        mockMvc.perform(post("/api/users")
            .contentType(MediaType.APPLICATION_JSON)
            .content("{\"name\":\"Anna Nowak\",\"email\":\"anna@example.com\"}"))
            .andExpect(status().isCreated())
            .andExpect(jsonPath("$.id").value(2))
            .andExpect(jsonPath("$.name").value("Anna Nowak"));
    }
}

Pro tip: @WebMvcTest automatycznie konfiguruje MockMvc i mockuje wszystkie serwisy. Nie musisz ręcznie tworzyć mocków – wystarczy @MockBean.

## @DataJpaTest – Testowanie Warstwy Danych

@DataJpaTest ładuje tylko konfigurację JPA: repozytoria, Entity Manager, bazy danych testowe. Automatycznie konfiguruje H2 in-memory database.

### Przykład repozytorium

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false)
    private String name;
    
    @Column(unique = true)
    private String email;
    
    // konstruktory, gettery, settery
}

@Repository
public interface UserRepository extends JpaRepository {
    Optional findByEmail(String email);
    List findByNameContainingIgnoreCase(String name);
}

### Test z @DataJpaTest

@DataJpaTest
public class UserRepositoryTest {
    
    @Autowired
    private TestEntityManager entityManager;
    
    @Autowired
    private UserRepository userRepository;
    
    @Test
    public void shouldFindUserByEmail() {
        // Given
        User user = new User("Jan Kowalski", "jan@example.com");
        entityManager.persistAndFlush(user);
        
        // When
        Optional found = userRepository.findByEmail("jan@example.com");
        
        // Then
        assertThat(found).isPresent();
        assertThat(found.get().getName()).isEqualTo("Jan Kowalski");
    }
    
    @Test
    public void shouldFindUsersByNameFragment() {
        // Given
        entityManager.persistAndFlush(new User("Jan Kowalski", "jan@example.com"));
        entityManager.persistAndFlush(new User("Anna Kowalska", "anna@example.com"));
        entityManager.persistAndFlush(new User("Piotr Nowak", "piotr@example.com"));
        
        // When
        List users = userRepository.findByNameContainingIgnoreCase("kowal");
        
        // Then
        assertThat(users).hasSize(2);
        assertThat(users).extracting(User::getName)
            .containsExactlyInAnyOrder("Jan Kowalski", "Anna Kowalska");
    }
}

## Inne Przydatne Test Slices

### @JsonTest – Testowanie serializacji JSON

@JsonTest
public class UserJsonTest {
    
    @Autowired
    private JacksonTester json;
    
    @Test
    public void shouldSerializeUser() throws Exception {
        User user = new User(1L, "Jan Kowalski", "jan@example.com");
        
        assertThat(json.write(user))
            .hasJsonPathStringValue("@.name", "Jan Kowalski")
            .hasJsonPathStringValue("@.email", "jan@example.com");
    }
}

### @WebFluxTest – Testowanie WebFlux (reaktywne)

@WebFluxTest(UserController.class)
public class UserControllerWebFluxTest {
    
    @Autowired
    private WebTestClient webClient;
    
    @MockBean
    private UserService userService;
    
    @Test
    public void shouldReturnUser() {
        User user = new User(1L, "Jan", "jan@example.com");
        when(userService.findById(1L)).thenReturn(Mono.just(user));
        
        webClient.get().uri("/api/users/1")
            .exchange()
            .expectStatus().isOk()
            .expectBody(User.class)
            .value(u -> assertThat(u.getName()).isEqualTo("Jan"));
    }
}

## Porównanie z @SpringBootTest

Aspekt@SpringBootTestTest Slices
Czas startu30-60 sekund2-5 sekund
KomponentyCała aplikacjaTylko potrzebna warstwa
Typ testuIntegracyjnyJednostkowy warstwy
Zużycie pamięciWysokieNiskie
MockowanieRzadko potrzebneAutomatyczne
Używaj @SpringBootTest dla testów end-to-end całych scenariuszy biznesowych. Test Slices dla testów jednostkowych konkretnych warstw.

## Najczęsze błędy początkujących

Typowy błąd: Mieszanie @WebMvcTest z rzeczywistymi serwisami. @WebMvcTest mockuje wszystkie beany poza kontrolerami – jeśli potrzebujesz prawdziwego serwisu, użyj @SpringBootTest.
Uwaga: Test Slices nie ładują komponentów @Configuration. Jeśli twój kontroler wymaga specjalnej konfiguracji, dodaj @Import(MojaKonfiguracja.class).
Który Test Slice wybrać dla mojego przypadku?

@WebMvcTest dla kontrolerów REST, @DataJpaTest dla repozytoriów, @JsonTest dla serializacji, @WebFluxTest dla reaktywnych kontrolerów. Jeśli testujesz logikę biznesową – zwykły @Test z mockami.

Czy mogę użyć prawdziwej bazy danych z @DataJpaTest?

Tak, dodaj @AutoConfigureTestDatabase(replace = Replace.NONE) i skonfiguruj profil testowy z prawdziwą bazą. Ale pamiętaj – testy będą wolniejsze.

Dlaczego mój @MockBean nie działa?

@MockBean działa tylko w Test Slices. W zwykłych testach używaj @Mock z Mockito i @ExtendWith(MockitoExtension.class).

Jak przetestować security z @WebMvcTest?

Dodaj @Import(SecurityConfig.class) lub użyj @WithMockUser dla prostych przypadków. Dla skomplikowanego security lepszy @SpringBootTest.

Czy Test Slices działają z JUnit 5?

Tak! Spring Boot 2.2+ domyślnie używa JUnit 5. Wszystkie przykłady działają z @ExtendWith(SpringExtension.class) który jest już w Test Slices.

## Przydatne zasoby

– [Spring Boot Testing Documentation](https://docs.spring.io/spring-boot/docs/2.1.x/reference/html/boot-features-testing.html)
– [JUnit 5 User Guide](https://junit.org/junit5/docs/current/user-guide/)
– [Mockito Documentation](https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html)
– [TestContainers – Integration testing](https://www.testcontainers.org/)

🚀 Zadanie dla Ciebie

Stwórz prostą aplikację Spring Boot z kontrolerem produktów i repozytorium. Napisz testy używając @WebMvcTest i @DataJpaTest. Zmierz czas wykonania testów i porównaj z @SpringBootTest. Jakie są różnice w czasie i zużyciu pamięci?

Jakie Test Slices najczęściej używasz w swoich projektach? Czy zauważyłeś znaczną poprawę czasu wykonania testów?

Zostaw komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Przewijanie do góry