← Kursa Dön
📄 Text · 28 min

@WebMvcTest ve MockMvc

@WebMvcTest, Spring Boot'un slice test konseptinin web katmanı için olan versiyonudur. Tam application context yerine sadece web katmanını (controller, filter, advice, converter) ayağa kaldırır. Service ve repository bean'leri yüklenmez — @MockBean ile mock'lanır. Bu sayede controller testleri çok hızlı çalışır.

@WebMvcTest Temelleri

@WebMvcTest(UserController.class) // Sadece bu controller'ı yükle
class UserControllerTest {

    @Autowired
    private MockMvc mockMvc; // HTTP istekleri simüle eder

    @MockBean
    private UserService userService; // Service mock'lanır

    @Test
    @DisplayName("GET /api/users/{id} — başarılı yanıt")
    void shouldReturnUser() throws Exception {
        UserDto user = new UserDto(1L, "Ali", "ali@test.com");
        when(userService.getUserById(1L)).thenReturn(user);

        mockMvc.perform(get("/api/users/1")
                .contentType(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.name").value("Ali"))
            .andExpect(jsonPath("$.email").value("ali@test.com"));
    }
}

@WebMvcTest sadece şunları yükler:

  • @Controller, @RestController

  • @ControllerAdvice

  • @JsonComponent

  • Filter, WebMvcConfigurer

  • HandlerMethodArgumentResolver

Yüklemedikleri: @Service, @Repository, @Component

MockMvc — HTTP İstek Simülasyonu

@WebMvcTest(ProductController.class)
class ProductControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private ProductService productService;

    // ─── GET Testleri ──────────────────────────────────────

    @Test
    void shouldReturnAllProducts() throws Exception {
        List<ProductDto> products = List.of(
            new ProductDto(1L, "Laptop", 999.99),
            new ProductDto(2L, "Mouse", 29.99)
        );
        when(productService.findAll()).thenReturn(products);

        mockMvc.perform(get("/api/products"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$").isArray())
            .andExpect(jsonPath("$.length()").value(2))
            .andExpect(jsonPath("$[0].name").value("Laptop"))
            .andExpect(jsonPath("$[1].price").value(29.99));
    }

    @Test
    void shouldReturn404WhenProductNotFound() throws Exception {
        when(productService.findById(999L))
            .thenThrow(new ProductNotFoundException("Not found"));

        mockMvc.perform(get("/api/products/999"))
            .andExpect(status().isNotFound())
            .andExpect(jsonPath("$.message").value("Not found"));
    }

    // ─── POST Testleri ─────────────────────────────────────

    @Test
    void shouldCreateProduct() throws Exception {
        ProductDto created = new ProductDto(1L, "Laptop", 999.99);
        when(productService.create(any())).thenReturn(created);

        String requestBody = """
            {
                "name": "Laptop",
                "price": 999.99
            }
            """;

        mockMvc.perform(post("/api/products")
                .contentType(MediaType.APPLICATION_JSON)
                .content(requestBody))
            .andExpect(status().isCreated())
            .andExpect(jsonPath("$.id").value(1))
            .andExpect(jsonPath("$.name").value("Laptop"))
            .andExpect(header().exists("Location"));
    }

    @Test
    void shouldReturn400ForInvalidProduct() throws Exception {
        // @Valid ile doğrulama — name boş olamaz
        String invalidBody = """
            {
                "name": "",
                "price": -5
            }
            """;

        mockMvc.perform(post("/api/products")
                .contentType(MediaType.APPLICATION_JSON)
                .content(invalidBody))
            .andExpect(status().isBadRequest())
            .andExpect(jsonPath("$.errors").isArray())
            .andExpect(jsonPath("$.errors.length()").value(
                greaterThanOrEqualTo(1)));
    }

    // ─── PUT Testleri ──────────────────────────────────────

    @Test
    void shouldUpdateProduct() throws Exception {
        ProductDto updated = new ProductDto(1L, "Gaming Laptop", 1499.99);
        when(productService.update(eq(1L), any())).thenReturn(updated);

        mockMvc.perform(put("/api/products/1")
                .contentType(MediaType.APPLICATION_JSON)
                .content("""
                    {"name": "Gaming Laptop", "price": 1499.99}
                    """))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.name").value("Gaming Laptop"));
    }

    // ─── DELETE Testleri ───────────────────────────────────

    @Test
    void shouldDeleteProduct() throws Exception {
        doNothing().when(productService).delete(1L);

        mockMvc.perform(delete("/api/products/1"))
            .andExpect(status().isNoContent());

        verify(productService).delete(1L);
    }
}

jsonPath ile Detaylı Doğrulama

JsonPath, JSON yanıtının belirli alanlarını sorgulamak için XPath benzeri bir dil kullanır:

// Temel erişim
.andExpect(jsonPath("$.name").value("Ali"))

// Array elemanları
.andExpect(jsonPath("$[0].name").value("Ali"))

// Array boyutu
.andExpect(jsonPath("$.length()").value(5))

// İç içe nesneler
.andExpect(jsonPath("$.address.city").value("Istanbul"))

// Wildcard
.andExpect(jsonPath("$[*].name").isArray())

// Matcher'lar
.andExpect(jsonPath("$.price").value(greaterThan(100.0)))
.andExpect(jsonPath("$.name").value(containsString("Lap")))
.andExpect(jsonPath("$.tags").isEmpty())
.andExpect(jsonPath("$.deletedAt").doesNotExist())

// content().json() ile tam JSON karşılaştırma
.andExpect(content().json("""
    {"id": 1, "name": "Laptop", "price": 999.99}
    """))

// content().json(expected, true) → strict mode (ekstra alanlar hata verir)

@MockBean vs @Mock

Özellik@Mock@MockBean
FrameworkMockitoSpring Boot Test
ContextSpring context dışıSpring context içi bean'i değiştirir
Kullanım@ExtendWith(MockitoExtension.class)@WebMvcTest, @SpringBootTest
EtkiSadece test sınıfındaApplication context'teki bean'i replace eder
PerformansÇok hızlıContext yeniden oluşturabilir
// @Mock — Spring context olmadan, saf birim test
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
    @Mock private UserRepository userRepository;
    @InjectMocks private UserService userService;
}

// @MockBean — Spring context içinde bean replace
@WebMvcTest(UserController.class)
class UserControllerTest {
    @MockBean private UserService userService; // Context'teki bean replace edilir
}

Response Yazdırma (Debug)

Test geliştirirken yanıtı görmek isterseniz:

mockMvc.perform(get("/api/users/1"))
    .andDo(print()) // Console'a tam HTTP request/response yazar
    .andExpect(status().isOk());

@WebMvcTest + MockMvc, controller katmanını hızlı ve izole bir şekilde test etmenin en etkili yoludur. JSON request/response doğrulama, hata durumu testleri ve validation kontrolü — tüm web katmanı endişeleri bu yapıyla kapsanır.