Używam Mockito w niektórych testach.
Mam następujące zajęcia:
class BaseService {
public void save() {...}
}
public Childservice extends BaseService {
public void save(){
//some code
super.save();
}
}
Chcę kpić tylko z drugiego wywołania ( super.save
) z ChildService
. Pierwsze wywołanie musi wywołać prawdziwą metodę. Czy jest na to sposób?
Odpowiedzi:
Nie, Mockito tego nie obsługuje.
To może nie być odpowiedź, której szukasz, ale to, co widzisz, jest symptomem niestosowania zasady projektowania:
Jeśli wyodrębnisz strategię zamiast rozszerzać superklasę, problem zniknie.
Jeśli jednak nie możesz zmienić kodu, ale i tak musisz go przetestować, w ten niezręczny sposób, nadal jest nadzieja. Za pomocą niektórych narzędzi AOP (na przykład AspectJ) możesz wplecić kod w metodę superklasy i całkowicie uniknąć jej wykonywania (fuj). To nie działa, jeśli używasz proxy, musisz użyć modyfikacji kodu bajtowego (albo splatanie czasu ładowania, albo tkanie czasu kompilacji). Istnieją również frameworki, które obsługują tego typu sztuczki, takie jak PowerMock i PowerMockito.
Proponuję zdecydować się na refaktoryzację, ale jeśli nie jest to opcja, czeka Cię poważna zabawa w hakowanie.
źródło
//some codes
kod do metody, którą można przetestować oddzielnie.Jeśli naprawdę nie masz wyboru co do refaktoryzacji, możesz mock / stub wszystko w wywołaniu super metody, np
class BaseService { public void validate(){ fail(" I must not be called"); } public void save(){ //Save method of super will still be called. validate(); } } class ChildService extends BaseService{ public void load(){} public void save(){ super.save(); load(); } } @Test public void testSave() { ChildService classToTest = Mockito.spy(new ChildService()); // Prevent/stub logic in super.save() Mockito.doNothing().when((BaseService)classToTest).validate(); // When classToTest.save(); // Then verify(classToTest).load(); }
źródło
BaseService
jest abstrakcyjny, chociaż nie rozumiem, dlaczego miałoby to mieć znaczenie.super.validate()
Rozważ refaktoryzację kodu z metody ChildService.save () na inną metodę i przetestuj tę nową metodę zamiast testowania ChildService.save (), w ten sposób unikniesz niepotrzebnego wywoływania metody super.
Przykład:
class BaseService { public void save() {...} } public Childservice extends BaseService { public void save(){ newMethod(); super.save(); } public void newMethod(){ //some codes } }
źródło
utwórz metodę chronioną pakietem (zakłada klasę testową w tym samym pakiecie) w klasie podrzędnej, która wywołuje metodę superklasy, a następnie wywołaj tę metodę w swojej przesłoniętej metodzie podklasy. możesz następnie określić oczekiwania dotyczące tej metody w swoim teście, używając wzorca szpiegowskiego. nie ładna, ale z pewnością lepsza niż konieczność radzenia sobie z wszystkimi oczekiwaniami dotyczącymi super metody w twoim teście
źródło
Nawet jeśli całkowicie zgadzam się z odpowiedzią iwein (
), przyznaję, że czasami dziedziczenie wydaje się po prostu naturalne i nie czuję, żebym go łamał lub refaktoryzował tylko ze względu na test jednostkowy.
Tak więc moja sugestia:
/** * BaseService is now an asbtract class encapsulating * some common logic callable by child implementations */ abstract class BaseService { protected void commonSave() { // Put your common work here } abstract void save(); } public ChildService extends BaseService { public void save() { // Put your child specific work here // ... this.commonSave(); } }
A potem w teście jednostkowym:
ChildService childSrv = Mockito.mock(ChildService.class, Mockito.CALLS_REAL_METHODS); Mockito.doAnswer(new Answer<Void>() { @Override public Boolean answer(InvocationOnMock invocation) throws Throwable { // Put your mocked behavior of BaseService.commonSave() here return null; } }).when(childSrv).commonSave(); childSrv.save(); Mockito.verify(childSrv, Mockito.times(1)).commonSave(); // Put any other assertions to check child specific work is done
źródło
Powodem jest to, że twoja klasa bazowa nie jest publicznie dostępna, a Mockito nie może jej przechwycić ze względu na widoczność, jeśli zmienisz klasę bazową na publiczną lub @Override w klasie podrzędnej (jako publiczną), wtedy Mockito może ją poprawnie kpić.
public class BaseService{ public boolean foo(){ return true; } } public ChildService extends BaseService{ } @Test @Mock ChildService childService; public void testSave() { Mockito.when(childService.foo()).thenReturn(false); // When assertFalse(childService.foo()); }
źródło
Może najłatwiejszą opcją, jeśli dziedziczenie ma sens, jest utworzenie nowej metody (pakiet prywatny ??), aby wywołać super (nazwijmy to superFindall), szpiegować prawdziwą instancję, a następnie kpić z metody superFindAll () w sposób, w jaki chciałeś kpić pierwsza klasa rodzicielska. Nie jest to idealne rozwiązanie pod względem zasięgu i widoczności, ale powinno wystarczyć i jest łatwe w aplikacji.
public Childservice extends BaseService { public void save(){ //some code superSave(); } void superSave(){ super.save(); } }
źródło