Mockito Jak kpić tylko z wywołania metody nadklasy


95

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?


Czy można to rozwiązać za pomocą PowerMockito?
javaPlease42

@ javaPlease42: Tak, możesz: stackoverflow.com/a/23884011/2049986 .
Jacob van Lingen

Odpowiedzi:


57

Nie, Mockito tego nie obsługuje.

To może nie być odpowiedź, której szukasz, ale to, co widzisz, jest symptomem niestosowania zasady projektowania:

Przedkładaj kompozycję nad dziedziczenie

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.


5
Nie widzę naruszenia LSP. Mam mniej więcej taką samą konfigurację jak OP: podstawową klasę DAO z metodą findAll () i podklasę DAO, która zastępuje metodę podstawową, wywołując super.findAll (), a następnie sortując wynik. Podklasę można zastąpić we wszystkich kontekstach akceptujących nadklasę. Czy źle zrozumiałem twoje znaczenie?

1
Usunę uwagę LSP (nie dodaje wartości do odpowiedzi).
Iwein

Tak, dziedziczenie jest do bani, a głupie ramy, w których utknąłem, są zaprojektowane z dziedziczeniem jako jedyną opcją.
Sridhar Sarnobat

Zakładając, że nie możesz przeprojektować nadklasy, możesz wyodrębnić //some codeskod do metody, którą można przetestować oddzielnie.
Phasmal

1
Ok, rozumiem. To inny problem niż ten, który próbowałem rozwiązać, kiedy tego szukałem, ale przynajmniej to nieporozumienie rozwiązało mój własny problem, aby udawać wywołanie z klasy bazowej (którego faktycznie nie nadpisuję).
Guillaume Perrot

89

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();
    }

2
ten kod w rzeczywistości nie uniemożliwiłby prawidłowego wywołania super.save (), więc jeśli robisz dużo w super.save (), musiałbyś zapobiec tym wszystkim wywołaniom ...
iwein

fantastyczne rozwiązanie działa na mnie cuda, gdy chcę zwrócić fałszywą wartość z metody nadklasy do wykorzystania przez dziecko, fantastycznie dziękuję.
Gurnard

14
działa to dobrze, chyba że walidacja jest ukryta lub metoda save działa bezpośrednio, zamiast wywoływać inną metodę. mockito nie: Mockito.doNothing (). when ((BaseService) spy) .save (); to nie `` nic nie zrobi w podstawowej usłudze save, ale na childService save :(
tibi

Nie mogę zmusić tego do pracy na moim - to również eliminuje metodę potomną. BaseServicejest abstrakcyjny, chociaż nie rozumiem, dlaczego miałoby to mieć znaczenie.
Sridhar Sarnobat

1
@ Sridhar-Sarnobat tak, widzę to samo :( ktoś wie, jak to zrobić, aby tylko super.validate()
zdusić

4

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
    }
} 

1

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


czy mogę też powiedzieć, że kompozycja zamiast dziedziczenia jest prawie zawsze lepsza, ale czasami po prostu prostsze jest użycie dziedziczenia. dopóki java nie zastosuje lepszego modelu kompozycyjnego, takiego jak scala lub groovy, zawsze tak będzie i ten problem będzie nadal istniał
Luke

1

Nawet jeśli całkowicie zgadzam się z odpowiedzią iwein (

przedkładają kompozycję nad dziedziczenie

), 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

0

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());
}

8
Nie o to chodzi. ChildService powinno zastąpić foo (), a problem polega na tym, jak wyszydzić BaseService.foo (), ale nie ChildService.foo ()
Adriaan Koster

0

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();
    }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.