## 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
## 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.
## @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 ResponseEntitygetUser(@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")); } }
## @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 Optionalfound = 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 JacksonTesterjson; @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 | @SpringBootTest | Test Slices |
---|---|---|
Czas startu | 30-60 sekund | 2-5 sekund |
Komponenty | Cała aplikacja | Tylko potrzebna warstwa |
Typ testu | Integracyjny | Jednostkowy warstwy |
Zużycie pamięci | Wysokie | Niskie |
Mockowanie | Rzadko potrzebne | Automatyczne |
## Najczęsze błędy początkujących
@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.
Tak, dodaj @AutoConfigureTestDatabase(replace = Replace.NONE) i skonfiguruj profil testowy z prawdziwą bazą. Ale pamiętaj – testy będą wolniejsze.
@MockBean działa tylko w Test Slices. W zwykłych testach używaj @Mock z Mockito i @ExtendWith(MockitoExtension.class).
Dodaj @Import(SecurityConfig.class) lub użyj @WithMockUser dla prostych przypadków. Dla skomplikowanego security lepszy @SpringBootTest.
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?