27 Nisan 2020 Pazartesi

SpringBoot Test @WebMvcTest Anotasyonu - Tek Bir Controller veya Tüm Controller'lar Test Edilir

Giriş
Şu satırı dahil ederiz.
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
@WebMvcTest Sliced Test için kullanılan anotasyonlardan bir tanesi. 

Açıklaması şöyle. Uygulamayı daha küçük parçalara ayırarak test işlemini kolaylaştırır
Typical applications have multiple layers and you want to write unit tests for different layers — web, business, and data.

Here are some of the recommended options:
- Web layer — Spring MockMVC
- Data layer — DataJpaTest
- Business layer — Mockito-based test preferably without launching a Spring Context
Bu anotasyon ile sadece Controller katmanındaki bean'leri yüklenir. Açıklaması şöyle.
@SpringBootTest annotation will load the fully ApplicationContext. Therefore it is highly used for writing the integration testing in web server environment. This will not use slicing and scan for all the stereotype annotations (@Component, @Service, @Respository and @Controller / @RestController) and loads the full application context. Therefore this is more good at in the context of writing integration testing for the application.

@WebMvcTest annotation will load only the controller layer of the application. This will scan only the @Controller/ @RestController annotation and will not load the fully ApplicationContext. If there is any dependency in the controller layer (has some dependency to other beans from your service layer), you need to provide them manually by mocking those objects.

Therefore @SpringBootTest is widely used for Integration Testing purpose and @WebMvcTest is used for controller layer Unit testing.
Hangi Anotasyonları Yükler
Açıklama şöyle.
To test whether Spring MVC controllers are working as expected, use the @WebMvcTest annotation. @WebMvcTest auto-configures the Spring MVC infrastructure and limits scanned beans to @Controller, @ControllerAdvice, @JsonComponent, Converter, GenericConverter, Filter, WebMvcConfigurer, and HandlerMethodArgumentResolver. Regular @Component beans are not scanned when using this annotation.
Yani şunlar
@Controller
@ControllerAdvice
@JsonComponent
Converter
GenericConverter
Filter
WebMvcConfigurer
HandlerMethodArgumentResolver
Spring Security

@AutoConfigureWebMvc
@AutoConfigureMockMvc : Test içinde MockMvc kullanılabilmesinin edilmesinin sebebi budur

Hangi Anotasyonları Yüklemez
Şunları yüklemez
@Component
@Service
@Repository

Genel Kullanım
İki kullanım çeşidi var
1. Sadece belirtilen Controller yüklenir
2. Tüm Controller'lar yüklenir

Bunlardan hangisini yaparsak yapalım :
1. @WebMvcTest anotasyonu eklenir
Birim testi yapılacak Controller için test yaratılır ve test sınıfının üstüne @WebMvcTest anotasyonu eklenir. Açıklaması şöyle
By default, tests annotated with @WebMvcTest will also auto-configure Spring Security and MockMvc (include support for HtmlUnit WebClient and Selenium WebDriver). For more fine-grained control of MockMVC the @AutoConfigureMockMvc annotation can be used.
2. MockMvc nesnesi @Autowire edilir
- Test içinde bir MockMvc nesnesi @Autowire edilir. MockMvc nesnesi ile test edeceğimiz Controller tetiklenir.
3. @MockBean ile Mock Yaratılır
- Test içinde Controller tarafından kullanılan bean'ler @MockBean veya @Import ile teste dahil edilir.
- Bu anotasyon ile Spring Security de yüklenir. Açıklaması şöyle
Typically @WebMvcTest is used in combination with @MockBean or @Import to create any collaborators required by your @Controller beans.
Açıklaması şöyle
Any service, component or repository that our controller is dependent on by dependency injection is separately included, and mocked using @MockBean annotation. This annotation adds the mocked object, in spring context it is a bean, to the application context.
@MockBean anotasyonunu çalışması için test şöyle yazılmalıdır
@ExtendWith(SpringExtension.class)
Açıklaması şöyle
When the test requires a Spring Test Context ( to autowire a bean / use of @MockBean ) along with JUnit 5's Jupiter programming model use @ExtendWith(SpringExtension.class). This will support Mockito annotations as well through TestExecutionListeners.
Örnek
Eğer filtreler yüklenmesin istersek şöyle yaparız
@WebMvcTest(SomeController.class)
@AutoConfigureMockMvc(addFilters = false)
public class SomeControllerTest {
}

Kullanım Örnekleri
Örnek - @MockBean Kullanmak İstemiyorsak
@MockBean kullanmayan ve kullanan iki testin farkını görmek için şöyle yaparız. Bence @MockBean kullanmak daha kolay. Yoksa gerçek bean yaratmak gerekiyor.
@WebMvcTest
class CustomerControllerIntegrationTests {
  @Autowired
  private MockMvc mockMvc;
  @Configuration
  static class RepositoryConfiguration {
    @Bean
    CustomerRepository customers() {
      return mock(CustomerRepository.class);
    }
    @Bean
    OrderRepository orders() {
      return mock(OrderRepository.class);
    }
  }
}

@WebMvcTest
class CustomerControllerIntegrationTests {
  @Autowired
  private MockMvc mockMvc;
  @MockBean
  private CustomerRepository customers; 
  @MockBean
  private OrderRepository orders;
}
Örnek - GET
Şöyle yaparız. Burada FooController yüklenir. FooController tarafından ihtiyaç duyulan FooService bean'i mock'lanır. Mock bean'in vereceği cevaplar Mockito ile ayarlanır. Daha sonra MockMvc ile Controller tetiklenir ve istenilen cevabın gelip gelmediği kontrol edilir.
@RunWith(SpringRunner.class)
@WebMvcTest(FooController.class)
public class FooControllerTest {

  @Autowired
  private MockMvc mvc;

  @MockBean
  private FooService fooServiceMock;

  @Test
  public void testExample() throws Exception {
    Foo mockedFoo = new Foo("one", "two");

    Mockito.when(fooServiceMock.get(1))
      .thenReturn(mockedFoo);

    mvc.perform(get("foos/1")
      .accept(MediaType.TEXT_PLAIN))
      .andExpect(status().isOk())
      .andExpect(content().string("one two"));
    }

}
Örnek - Get + Create + Delete
Şöyle yaparız
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@ExtendWith(SpringExtension.class)
@WebMvcTest(OrderController.class)
public class OrderControllerUnitTest {

  @Autowired
  private MockMvc mockMvc;

  @MockBean
  private OrderService orderService;

  private Order order;
  private final ObjectMapper objectMapper = new ObjectMapper();

  @BeforeEach
  public void setup() {
    order = new Order(10L, "andrew", 40.0, 2);
  }

  @Test
  public void testGetOrdersList() throws Exception {
    when(orderService.getOrders()).thenReturn(Collections.singletonList(order));
    mockMvc.perform(get("/api/orders"))
      .andDo(print())
      .andExpect(status().isOk())
      .andExpect(content().contentType(MediaType.APPLICATION_JSON))
      .andExpect(jsonPath("$", hasSize(1)))
      .andExpect(jsonPath("$").isArray());
  }

  @Test
  public void testGetOrderById() throws Exception {
    when(orderService.getOrderById(10L)).thenReturn(order);
    mockMvc.perform(get("/api/orders/10"))
      .andDo(print())
      .andExpect(status().isOk())
      .andExpect(content().contentType(MediaType.APPLICATION_JSON))
      .andExpect(jsonPath("$.buyer", is("andrew")))
      .andExpect(jsonPath("$.id", is(10)))
      .andExpect(jsonPath("$").isNotEmpty());
  }
  ...
}
Create ve Delete için şöyle yaparız
@Test
public void testCreateOrder() throws Exception {
  when(orderService.createOrder(order)).thenReturn(order);
  mockMvc.perform(
    post("/api/orders")
      .content(objectMapper.writeValueAsString(order))
      .contentType(MediaType.APPLICATION_JSON)
  )
  .andDo(print())
  .andExpect(status().isCreated())
  .andExpect(content().contentType(MediaType.APPLICATION_JSON))
  .andExpect(jsonPath("$.buyer", is("andrew")))
  .andExpect(jsonPath("$.id", is(10)))
  .andExpect(jsonPath("$").isNotEmpty());
}

@Test
public void testDeleteOrder() throws Exception {
  when(orderService.deleteOrderById(order.getId())).thenReturn(true);
  mockMvc.perform(delete("/api/orders/" + order.getId()))
    .andDo(print())
    .andExpect(status().isOk());
}
controllers Alanı
Açıklaması şöyle.
Often @WebMvcTest will be limited to a single controller and used in combination with @MockBean to provide mock implementations for required collaborators.
Açıklaması şöyle
@WebMvcTest annotation takes controller as parameter, if no parameter given as controller, includes all of the controllers in the context.

excludeAutoConfiguration Alanı
Örnek
Şöyle yaparız. Böylece Spring Security yüklenmez.
@WebMvcTest(excludeAutoConfiguration = SecurityAutoConfiguration.class) 

Hiç yorum yok:

Yorum Gönder