Mock & MockBean & Spy & SpyBean
Mock & MockBean & Spy & SpyBean
Ten artykuł na celu wyjaśnić różnicę między 4 typami adnotacji @Mock & @MockBean & @Spy & @SpyBean. Na początek zacznijmy od pobrania projektu – https://github.com/mwarycha/JavaLeader.pl/tree/master/bean-rest-validation – jest to projekt w którym zdefiniowano obiekt domenowy – Employee wraz z podstawowymi walidacjami. Po więcej informacji zapraszam do artykułu – https://javaleader.pl/2021/10/13/rest-api-i-walidacja-beana/. Napiszmy testy integracyjne:
@WebMvcTest(EmployeeController.class) @Import(PrinterLog.class) public class EmployeeTest { @Autowired private MockMvc mockMvc; @Mock PrinterLog printerLog; @Test void validEmployeeSuccessObject() throws Exception { Mockito.when(printerLog.printSuccess("employee is valid")).thenReturn("[OK] employee is valid"); MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.post("/emp") .contentType(MediaType.APPLICATION_JSON) .content(jsonSampleSuccessData()); this.mockMvc.perform(builder) .andDo(result -> System.out.println(result.getResponse().getContentAsString())) .andExpect(status().isOk()); } private String jsonSampleSuccessData() { return "{\"name\":\"Java\",\"surname\":\"Leader\",\"salary\":1000,\"role\":\"DEV\"}"; } }
gdzie klasa PrinterLog:
@Component public class PrinterLog { public String printError(String error) { return "[ERROR] " + error; } public String printSuccess(String success) { return "[SUCCESS] " + success; } }
oraz RestController gdzie zdefiniowany jest endpoint który testujemy:
@RestController public class EmployeeController { @Autowired PrinterLog printerLog; @PostMapping("/emp") public ResponseEntity<String> addEmployee(@Valid @RequestBody Employee employee) { return ResponseEntity.ok(printerLog.printSuccess("employee is valid")); } }
Zastosowanie adnotacji Mock spowoduje, że obiekt PrinterLog będzie całkowicie atrapą obiektu co oznacza, że nie będzie posiadał żadnych metod z rzeczywistą implementacją. Bardzo ważne jest zastosowanie w powyższym przykładzie adnotacji @Import(PrinterLog.class). Jeśli jej nie użyjemy nie będziemy wstanie wstrzyknąć do testowanego kontrolera implementacji klasy PrinterLog co w konsekwencji zakończy się błędem:
Error creating bean with name 'employeeController': Unsatisfied dependency expressed through field 'printerLog';
wynika to z faktu, że klasa PrinterLog jest poza kontekstem Springa i musimy ją jawnie tutaj wskazać/zaimportować. Dodajmy linie:
System.out.println(printerLog.printSuccess("success")); System.out.println(printerLog.printError("error"));
do metody testowej w następujący sposób:
@Test void validEmployeeSuccessObject() throws Exception { Mockito.when(printerLog.printSuccess("employee is valid")).thenReturn("[OK] employee is valid"); System.out.println(printerLog.printSuccess("success")); System.out.println(printerLog.printError("error")); MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.post("/emp") .contentType(MediaType.APPLICATION_JSON) .content(jsonSampleSuccessData()); this.mockMvc.perform(builder) .andDo(result -> System.out.println(result.getResponse().getContentAsString())) .andExpect(status().isOk()); }
w wyniku otrzymamy:
null null [SUCCESS] employee is valid
można zadać sobie teraz pytanie – dlaczego w RestController użyta została rzeczywista implementacja a nie ta której zachowanie zdefiniowane zostało w linii niżej?
Mockito.when(printerLog.printSuccess("employee is valid")).thenReturn("[OK] employee is valid");
Wynika to z faktu, że spring używa rzeczywistej implementacji zaimportowanej klasy w swoim kontekście:
@Import(PrinterLog.class)
- Adnotacja @Mock nie powoduje, że spring będzie zarządzał obiektami klasy ją oznaczonych.
Zmieńmy to zachowanie i zastosujmy teraz adnotację @MockBean:
@MockBean
PrinterLog printerLog;
wynik:
null null [OK] employee is valid
Od teraz spring używa w swoim kontekście atrapy obiektu gdzie zdefiniowaliśmy zachowanie:
Mockito.when(printerLog.printSuccess("employee is valid")).thenReturn("[OK] employee is valid");
nie jest potrzebna adnotacja:
@Import(PrinterLog.class)
- ponieważ adnotacja @MockBean spowoduje, że spring będzie zarządzał obiektami klasy ją oznaczonych.
Zarówno adnotacja @Mock jak i @MockBean to adnotacje które tworzą atrapy obiektów. Różnica polega na tym, że @MockBean tworzy beana zarządzanego przez springa.
Zmieńmy to zachowanie i zastosujmy teraz adnotację @Spy – konieczne jest użycie adnotacji @Import(PrinterLog.class) z tego samego powodu co powód opisany dla adnotacji @Mock wyżej.
@Spy
PrinterLog printerLog;
wynik:
[SUCCESS] success [ERROR] error [SUCCESS] employee is valid
spring używa rzeczywistej implementacji zaimportowanej klasy w swoim kontekście:
@Import(PrinterLog.class)
Obiekty typu Spy to obiekty które nie są atrapami ale możemy modyfikować ich zachowanie.
- Adnotacja @Spy nie powoduje, że spring będzie zarządzał obiektami klasy ją oznaczonych.
Zmieńmy to zachowanie i zastosujmy teraz adnotację @SpyBean – nie jest wymagana adnotacja:
@Import(PrinterLog.class)
- ponieważ adnotacja @SpyBean spowoduje, że spring będzie zarządzał obiektami klasy ją oznaczonych.
wynik:
[OK] employee is valid [ERROR] error [OK] employee is valid
Od teraz spring używa w swoim kontekście rzeczywistego obiektu gdzie zmodyfikowaliśmy zachowanie:
Mockito.when(printerLog.printSuccess("employee is valid")).thenReturn("[OK] employee is valid");
Zarówno adnotacja @Spy jak i @SpyBean to adnotacje które tworzą rzeczywiste obiekty kórych zachowanie możemy nadpisać. Różnica polega na tym, że @SpyBean tworzy beana zarządzanego przez springa.
Super artykuł i wszystko jasne!