Junit ile Controller Testi
Unit test konusu kendi gözlediğim kadarıyla çoğu zaman yapılmak istenen fakat hiç yapılmayan bir pratik. Bunun nedeni çoğunlukla zaman! Yani en azından bahane zaman!
Test yüzü görmeyen projeler inanıyorum ki bir yerlerde hayatlarına devam ediyor.
Yazmak istiyoruz, inanıyoruz ama vaktimiz yok!
Unit Test
Bir test methodu en temel anlamda üç adımdan oluşur. Farklı kaynaklarda farklı isimlendirmeler görülebilir fakat temel anlamda;
Given: testin ihtiyaç duyduğu ile ilgili
When: test edeceğimiz methodun çağrılması
Then: methoddan beklediğimiz işleri yapıp yapmadığını doğruladığımız bölüm.
Doğrulama, doğru değerlerin cevap olarak dönmesi, beklediğimiz hatanız fırlatılması ya da başka methodları çağırıp çağırmaması olabilir.
- Tek bir test methodu bir test senaryosunu gerçekleştirmelidir.
- Bir method için birden fazla test senaryosunu yazılabilir/ yazılmalıdır.
- Methodun bağımlılıkları sahte(mock) veriler ile sağlanır. Çünkü amaç methodun kendi görevini doğru yaptığını kontrol etmek.
Test Methodu İsimlendirme
İsimlendirme tabii ki burada da çok önemli. Kendi standardınızı oluşturabileceğiniz gibi, bir çok yerde görebileceğiniz standart isimlendirmeleri kullanabilirsiniz.
Kaldı ki böyle bir ihtiyacı testleriniz arttıkça kendiniz de gözlemleyeceksiniz.
Projelerde kullandığım standart aşağıdaki gibidir.
MethodName_StateUnderTest_ExpectedBehavior
dezavantaj: method ismi değişirse burası da değilmeli.
örnek: isAdult_AgeLessThan18_False
Bir çok projede gördüğüm isimlendirme ise aşağıdaki gibidir.
Should_ExpectedBehavior_When_StateUnderTest
dezavantaj: uzun ve should ve when kelimelerinin tekrarı
örnek: Should_ThrowException_When_AgeLessThan18
Diğer örnekler için aşağıdaki linklerden faydalanabilirsiniz.
https://medium.com/@stefanovskyi/unit-test-naming-conventions-dd9208eadbea
https://dzone.com/articles/7-popular-unit-test-naming
Controller Testi
Aşağıdaki Controller class’ı için test yazmayı deneyelim.
- JUnit 5
- Spring Boot 2.7.0
- Mockito
Method
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@RestController @RequestMapping(value = "/users") public class UserController { @Autowired private UserService userService; @GetMapping public ResponseEntity<List<UserResponse>> getAll() { return ResponseEntity.ok(userService.getAll()); } } |
Methodu incelediğimizde sadece başka bir methodu çağırdığını görüyoruz yani sadece başka bir methoda bağımlılığı var. Dolayısıyla buradan gelecek veri methodumuzun kendi işine odaklanmasını sağlacak. Bunun gibi bağımlılıklarımızın olduğu yerleri sahte(mock) veriler ile sağlıyor olacağız. Çünkü amacımız method kendi üstüne düşen görevi doğru yapıp yapmadığı.
Unit Test
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
package test.controller; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.util.List; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; @WebMvcTest(UserController.class) class UserControllerTest { @Autowired private MockMvc mockMvc; @MockBean private UserService userService; @Test void it_should_get_all_users() throws Exception { //Given Mockito.when(userService.getAll()).thenReturn(getUserResponseList()); //When ResultActions resultActions = mockMvc.perform(get("/users")); //Then //// @formatter:off resultActions .andExpect(status().isOk()) .andExpect(jsonPath("$[0].id").value(1)) .andExpect(jsonPath("$[0].name").value("test1")) .andExpect(jsonPath("$[0].type").value(UserType.INDIVIDUAL.toString())); // @formatter:on verify(userService, times(1)).getAll(); } private List<UserResponse> getUserResponseList() { return List.of(getUserResponse(1)); } private UserResponse getUserResponse(int id) { } } |
Method
Yine aynı şekilde çok benzer bir methodu inceleyelim ve testini yazmaya çalışalım.
UserService’in createUser methoduna bağımlı bir method görüyoruz ve aldığımız cevabı ResponseEntity objesine koyup dönüyoruz. Methodun görevi bu herhangi bir iş kuralı işletmiyor ve ya dallanma gerçekleşmeden görevini gerçekleştirmiyor.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@PostMapping public ResponseEntity<UserResponse> create(@RequestBody UserRequest userRequest) { Logger logger = Logger.getLogger(UserController.class.getName()); UserResponse userResponse = userService.createUser(userRequest); logger.log(Level.INFO, "user created: {0}", userResponse); return ResponseEntity.ok(userResponse); } |
Unit Test
Burada beklentimiz, bağımlı olduğu başka bir class’tan verinin gelmesi durumda kendi görevini doğru şekilde gerçekleştirebilmesi.
Peki bağımlı olduğu class’tan veri nasıl gelecek? Bu sorumuzun cevabı da mock! Test methodunda gördüğümüz gibi bağımlı olduğumuz method bizim istediğimiz değerler ile çağrıldığında bizim hazırladığımız bir obje cevap olarak dönüyor. Sonuç olarak artık beklentimiz methodun kendi görevini gerçekleştirebilmesi yani bu cevabı bize dönebilmesi gerek. Eğer biz de bu verilerin doğru geldiğini ispatlayabilirsek doğru bir test senaryosunu gerçekleştirmiş oluruz.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
@Test void it_should_create() throws Exception { //Given Mockito.when(userService.createUser(Mockito.any(UserRequest.class))).thenReturn(getUserResponse(1)); String request = mapper.writeValueAsString(getUserRequest()); //When ResultActions resultActions = mockMvc .perform(post("/users").content(request).contentType(MediaType.APPLICATION_JSON)); //Then resultActions.andExpect(status().isOk()) .andExpect(jsonPath("$.id").value(1)) .andExpect(jsonPath("$.name").value("test")) .andExpect(jsonPath("$.type").value(UserType.INDIVIDUAL.toString())); } |
Bir sonraki yazıda servis katmanına testler yazacağız, görüşmek üzere.
Faydalı olması dileğiyle.