Jak kpić z ostatniej klasy za pomocą mockito

218

Mam ostatnią klasę, coś takiego:

public final class RainOnTrees{

   public void startRain(){

        // some code here
   }
}

Korzystam z tej klasy w innej klasie, takiej jak ta:

public class Seasons{

   RainOnTrees rain = new RainOnTrees();

   public void findSeasonAndRain(){

        rain.startRain();

    }
}

i w mojej klasie testowej JUnit, Seasons.javabo chcę z niej kpić RainOnTrees. Jak mogę to zrobić za pomocą Mockito?

Buttowski
źródło
9
Mockito nie pozwala na to, jednak PowerMock na to pozwala.
fge
1
Począwszy od Mockito 2.x, Mockito obsługuje teraz drwiny z końcowych klas i metod.
Kent
Możliwy duplikat Ostatecznej klasy z Mockito 2
eliasah

Odpowiedzi:

155

Wyśmiewanie klas / metod statycznych / końcowych jest możliwe tylko w Mockito v2.

dodaj to do pliku oceny:

testImplementation 'org.mockito:mockito-inline:2.13.0'

Nie jest to możliwe w Mockito v1 z FAQ Mockito :

Jakie są ograniczenia Mockito

  • Potrzebuje java 1.5+

  • Nie można kpić z ostatnich klas

...

akshay bhange
źródło
2
Nie działało to dla mnie w Scali (z modyfikacjami sbt).
micseydel
2
To mi nie wystarczało. Musiałem także stworzyć src / test / resources / mockito-extensions / org.mockito.plugins.MockMaker z „mock-maker-inline” w nim jak na baeldung.com/mockito-final
micseydel
204

Mockito 2 obsługuje teraz końcowe klasy i metody!

Ale na razie jest to funkcja „inkubująca”. Aktywacja wymaga kilku kroków opisanych w części Co nowego w Mockito 2 :

Kpiny z końcowych klas i metod to inkubująca opcja akceptacji. Wykorzystuje kombinację instrumentacji agenta Java i podklasowania, aby umożliwić wyśmiewanie tych typów. Ponieważ działa to inaczej niż nasz obecny mechanizm, a ten ma inne ograniczenia i ponieważ chcemy zebrać doświadczenie i opinie użytkowników, ta funkcja musiała zostać wyraźnie aktywowana, aby była dostępna; można to zrobić za pomocą mechanizmu rozszerzenia mockito, tworząc plik src/test/resources/mockito-extensions/org.mockito.plugins.MockMakerzawierający jedną linię:

mock-maker-inline

Po utworzeniu tego pliku Mockito automatycznie użyje tego nowego silnika i można:

 final class FinalClass {
   final String finalMethod() { return "something"; }
 }

 FinalClass concrete = new FinalClass(); 

 FinalClass mock = mock(FinalClass.class);
 given(mock.finalMethod()).willReturn("not anymore");

 assertThat(mock.finalMethod()).isNotEqualTo(concrete.finalMethod());

W kolejnych etapach zespół przyniesie programowy sposób korzystania z tej funkcji. Zidentyfikujemy i zapewnimy wsparcie dla wszystkich niemożliwych do wyrejestrowania scenariuszy. Bądź na bieżąco i daj nam znać, co myślisz o tej funkcji!

Powietrzny jeźdźca
źródło
14
Nadal pojawia
IgorGanapolsky
3
Upewnij się, że umieściłeś org.mockito.plugins.MockMakerplik we właściwym folderze.
WindRider
7
Pojawia się również błąd, nawet po przestrzeganiu powyższego: Mockito nie może kpić / szpiegować, ponieważ: - klasa ostateczna
rcde0
8
@vCillusion ta odpowiedź nie jest w żaden sposób związana z PowerMock.
Linia
6
Postępowałem zgodnie z tymi instrukcjami, ale nadal nie mogłem sprawić, by to zadziałało, czy ktoś musiał zrobić coś jeszcze?
Franco
43

Nie możesz kpić z ostatniej klasy za pomocą Mockito, ponieważ nie możesz tego zrobić sam.

To, co robię, to stworzyć nie-końcową klasę, aby owinąć klasę końcową i użyć jako delegata. Przykładem tego jest TwitterFactoryklasa, a to moja próbna klasa:

public class TwitterFactory {

    private final twitter4j.TwitterFactory factory;

    public TwitterFactory() {
        factory = new twitter4j.TwitterFactory();
    }

    public Twitter getInstance(User user) {
        return factory.getInstance(accessToken(user));
    }

    private AccessToken accessToken(User user) {
        return new AccessToken(user.getAccessToken(), user.getAccessTokenSecret());
    }

    public Twitter getInstance() {
        return factory.getInstance();
    }
}

Wadą jest to, że istnieje wiele kodów typu „Boiler Plate”; zaletą jest to, że możesz dodać niektóre metody, które mogą odnosić się do Twojej działalności związanej z aplikacjami (np. getInstance, która bierze użytkownika zamiast accessToken, w powyższym przypadku).

W twoim przypadku stworzyłbym nie-końcową RainOnTreesklasę, która delegowałaby się do klasy ostatecznej. Lub, jeśli możesz sprawić, że będzie niefinałowy, byłoby lepiej.

Luigi R. Viggiano
źródło
6
+1. W razie potrzeby możesz użyć czegoś takiego jak Lombok @Delegatedo obsługi dużej części płyty kotłowej.
ruakh
2
@luigi możesz dodać fragment kodu dla Junit jako przykład. Próbowałem stworzyć Wrapper dla mojej ostatniej klasy, ale nie mogłem się dowiedzieć, jak to przetestować.
Incredible
31

dodaj to do pliku oceny:

testImplementation 'org.mockito:mockito-inline:2.13.0'

jest to konfiguracja umożliwiająca mockito pracę z klasami końcowymi

BennyP
źródło
1
Prawdopodobnie powinien teraz używać „testImplementation” zamiast „testCompile”. Gradle nie lubi już „testCompile”.
jwehrle
świetny komentarz, dzięki! edytowane do testImplementation. oryginalny komentarz: testCompile 'org.mockito: mockito-inline: 2.13.0'
BennyP
2
Powoduje to błąd podczas uruchamiania w systemie Linux / OpenJDK 1.8:org.mockito.exceptions.base.MockitoInitializationException: Could not initialize inline Byte Buddy mock maker. (This mock maker is not supported on Android.)
naXa
Działa dobrze po przełączeniu na Oracle JDK 1.8
naXa
23

Użyj Powermock. Ten link pokazuje, jak to zrobić: https://github.com/jayway/powermock/wiki/MockFinal

Gábor Lipták
źródło
30
Myślę, że PowerMock jest jak jeden z tych leków, które powinny wychodzić wyłącznie na receptę. W sensie: należy wyjaśnić, że PowerMock ma wiele problemów; i że używanie go jest jak ostateczność; i należy go w jak największym stopniu unikać.
GhostCat
1
dlaczego to mówisz?
PragmaticProgrammer
Używałem Powermockdla wyśmianie końcowych klas i metody statyczne, aby zwiększyć zasięg mojego, który został urzędowo sprawdzane na Sonarqube. Pokrycie wynosiło 0% od SonarQube, z jakiegokolwiek powodu nie rozpoznaje klas, które używają Powermock nigdzie w nim. Zajęło mi to trochę czasu wraz z zespołem, aby zdać sobie z tego sprawę z jakiegoś wątku online. To tylko jeden z powodów, dla których powinieneś być ostrożny z Powermock i prawdopodobnie go nie używaj.
amer
16

Tylko po to. Dodaj ten wiersz do pliku oceny:

testCompile group: 'org.mockito', name: 'mockito-inline', version: '2.8.9'

Próbowałem różnych wersji mockito-core i mockito-all. Żadne z nich nie działa.

Michael_Zhang
źródło
1
Aby dodać do tego, zauważyłem jedną rzecz, że jeśli używasz Powermock razem z mockito; następnie dodanie pliku wtyczki mockmaker w 'src / test / resources / mockito-extensions / org.mockito.plugins.MockMaker' nie byłoby przydatne w wyśmiewaniu końcowych klas. Zamiast tego dodanie zależności, o której wspominał Michael_Zhang powyżej, rozwiązałoby problem wyśmiewania klas końcowych. Upewnij się także, że używasz Mockito 2 zamiast Mockito1
dishooom
12

Myślę, że udało ci się, finalponieważ chcesz zapobiec rozszerzaniu innych klas RainOnTrees. Jak sugeruje Effective Java (punkt 15), istnieje inny sposób na utrzymanie klasy blisko do rozszerzenia bez robienia tego final:

  1. Usuń finalsłowo kluczowe;

  2. Zrób jego konstruktora private. Żadna klasa nie będzie w stanie go rozszerzyć, ponieważ nie będzie w stanie wywołać superkonstruktora;

  3. Utwórz statyczną metodę fabryczną, aby utworzyć instancję klasy.

    // No more final keyword here.
    public class RainOnTrees {
    
        public static RainOnTrees newInstance() {
            return new RainOnTrees();
        }
    
    
        private RainOnTrees() {
            // Private constructor.
        }
    
        public void startRain() {
    
            // some code here
        }
    }

Korzystając z tej strategii, będziesz mógł korzystać z Mockito i utrzymywać klasę zamkniętą, aby móc ją rozszerzać za pomocą niewielkiego kodu.

Flávio Faria
źródło
1
nie działa to w przypadku ostatecznych metod, które można również wyśmiewać za pomocą mockito 2.
Łukasz Rzeszotarski
11

Miałem ten sam problem. Ponieważ klasa, którą próbowałem kpić, była prostą klasą, po prostu stworzyłem jej instancję i zwróciłem ją.

użytkownik1945457
źródło
2
Oczywiście, po co kpić z prostej klasy? Wyśmiewanie dotyczy „drogich” interakcji: inne usługi, silniki, klasy danych itp.
StripLight
3
Jeśli utworzysz taką instancję, nie możesz później zastosować do niej metod Mockito.verify. Głównym zastosowaniem prób jest możliwość przetestowania niektórych jej metod.
riroo
6

Wypróbuj to:

Mockito.mock(SomeMockableType.class,AdditionalAnswers.delegatesTo(someInstanceThatIsNotMockableOrSpyable));

To zadziałało dla mnie. „SomeMockableType.class” to klasa nadrzędna tego, co chcesz wyśmiewać lub szpiegować, a someInstanceThatIsNotMockableOrSpyable to klasa, którą chcesz wyśmiewać lub szpiegować.

Aby uzyskać więcej informacji, zobacz tutaj

sakis kaliakoudas
źródło
3
Należy zauważyć, że delegaci bardzo różnią się od natywnego drwiącego szpiega. W natywnym szpiegu mockito „to” w odniesieniu do instancji do samego szpiega (ponieważ jest to użycie podklasy) Jednak w delegacie „to” będzie prawdziwym obiektem someInstanceThatIsNotMockableOrSpyable. Nie szpieg. W związku z tym nie ma możliwości wykonania funkcji Zwrot / weryfikacja dla funkcji wywołujących.
Dennis C
1
czy możesz podać przykład?
Vishwa Ratna
5

Innym obejściem, które może mieć zastosowanie w niektórych przypadkach, jest utworzenie interfejsu, który jest implementowany przez tę ostatnią klasę, zmiana kodu w celu użycia interfejsu zamiast konkretnej klasy, a następnie wyśmiewanie interfejsu. Pozwala to oddzielić umowę (interfejs) od implementacji (klasa końcowa). Oczywiście, jeśli naprawdę chcesz powiązać się z klasą końcową, nie będzie to miało zastosowania.

andresp
źródło
5

Właściwie jest jeden sposób, którego używam do szpiegowania. Przydałoby się to tylko wtedy, gdy spełnione są dwa warunki wstępne:

  1. Używasz jakiegoś DI do wstrzykiwania instancji klasy końcowej
  2. Klasa końcowa implementuje interfejs

Proszę przywołać punkt 16 z Effective Java . Możesz utworzyć opakowanie (nie ostateczne) i przekazać wszystkie wywołania do instancji klasy końcowej:

public final class RainOnTrees implement IRainOnTrees {
    @Override public void startRain() { // some code here }
}

public class RainOnTreesWrapper implement IRainOnTrees {
    private IRainOnTrees delegate;
    public RainOnTreesWrapper(IRainOnTrees delegate) {this.delegate = delegate;}
    @Override public void startRain() { delegate.startRain(); }
}

Teraz możesz nie tylko kpić z ostatniej klasy, ale także szpiegować ją:

public class Seasons{
    RainOnTrees rain;
    public Seasons(IRainOnTrees rain) { this.rain = rain; };
    public void findSeasonAndRain(){
        rain.startRain();
   }
}

IRainOnTrees rain = spy(new RainOnTreesWrapper(new RainOnTrees()) // or mock(IRainOnTrees.class)
doNothing().when(rain).startRain();
new Seasons(rain).findSeasonAndRain();
Xuesheng
źródło
5

W Mockito 3 i więcej mam ten sam problem i naprawiłem go z tego linku

Wyśmiewaj końcowe klasy i metody za pomocą Mockito w następujący sposób

Zanim Mockito będzie można wykorzystać do kpienia z końcowych klas i metod, należy go> skonfigurować.

Musimy dodać plik tekstowy do katalogu projektu src / test / resources / mockito-extensions o nazwie org.mockito.plugins.MockMaker i dodać jeden wiersz tekstu:

mock-maker-inline

Mockito sprawdza, czy w katalogu rozszerzeń nie ma plików konfiguracyjnych podczas ładowania. Ten plik umożliwia wyśmiewanie końcowych metod i klas.

Sattar
źródło
4

Oszczędność czasu dla osób, które borykają się z tym samym problemem (Mockito + klasa ostateczna) na Androidzie + Kotlin. Podobnie jak w przypadku Kotlin klasy są domyślnie ostateczne. Znalazłem rozwiązanie w jednym z przykładów Google Android z komponentem Architecture. Rozwiązanie wybrane tutaj: https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample

Utwórz następujące adnotacje:

/**
 * This annotation allows us to open some classes for mocking purposes while they are final in
 * release builds.
 */
@Target(AnnotationTarget.ANNOTATION_CLASS)
annotation class OpenClass

/**
 * Annotate a class with [OpenForTesting] if you want it to be extendable in debug builds.
 */
@OpenClass
@Target(AnnotationTarget.CLASS)
annotation class OpenForTesting

Zmodyfikuj plik oceny. Weź przykład z tego miejsca: https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/app/build.gradle

apply plugin: 'kotlin-allopen'

allOpen {
    // allows mocking for classes w/o directly opening them for release builds
    annotation 'com.android.example.github.testing.OpenClass'
}

Teraz możesz dodać adnotację do dowolnej klasy, aby była otwarta do testowania:

@OpenForTesting
class RepoRepository 
Ozeetee
źródło
Działa to dobrze na poziomie build.gradle na poziomie aplikacji, ale co możemy zrobić, aby uzyskać to na poziomie biblioteki?
Sumit T
Czy możesz trochę rozwinąć? Zwykle do łączenia się z bibliotekami używaj wzoru fasady. I wyśmiewać te klasy elewacji, aby przetestować aplikację. W ten sposób nie musimy wyśmiewać żadnych klas lib.
Ozeetee
3

Można to zrobić, jeśli używasz Mockito2, z nową funkcją inkubacji, która obsługuje wyśmiewanie końcowych klas i metod.

Najważniejsze kwestie do zapamiętania:
1. Utwórz prosty plik o nazwie „org.mockito.plugins.MockMaker” i umieść go w folderze o nazwie „rozszerzenia mockito”. Ten folder powinien zostać udostępniony w ścieżce klasy.
2. Treść pliku utworzonego powyżej powinna być pojedynczą linią, jak podano poniżej:
mock-maker-inline

Powyższe dwa kroki są wymagane w celu aktywacji mechanizmu rozszerzenia mockito i skorzystania z tej opcji opt-in.

Przykładowe klasy są następujące:

FinalClass.java

public final class FinalClass {

public final String hello(){
    System.out.println("Final class says Hello!!!");
    return "0";
}

}

Foo.java

public class Foo {

public String executeFinal(FinalClass finalClass){
    return finalClass.hello();
}

}

FooTest.java

public class FooTest {

@Test
public void testFinalClass(){
    // Instantiate the class under test.
    Foo foo = new Foo();

    // Instantiate the external dependency
    FinalClass realFinalClass = new FinalClass();

    // Create mock object for the final class. 
    FinalClass mockedFinalClass = mock(FinalClass.class);

    // Provide stub for mocked object.
    when(mockedFinalClass.hello()).thenReturn("1");

    // assert
    assertEquals("0", foo.executeFinal(realFinalClass));
    assertEquals("1", foo.executeFinal(mockedFinalClass));

}

}

Mam nadzieję, że to pomoże.

Pełny artykuł obecny tutaj drwiący z niemożliwego do wyśmiewania .

ksl
źródło
Powinieneś dołączyć odpowiedź tutaj, a nie link do strony zewnętrznej. Jeśli procedura jest długa, możesz dołączyć przegląd.
rghome
upewnij się, że poniższe adnotacje są używane podczas kpienia z @RunWith (PowerMockRunner.class) @PrepareForTest ({AFinalClass.class})
vCillusion
1
@vCillusion - pokazany przeze mnie przykład wykorzystuje API Mockito2. Korzystając z opcji opt-in Mockito2, można bezpośrednio wyśmiewać ostatnie klasy bez konieczności korzystania z Powermock.
ksl
2

Tak, ten sam problem tutaj, nie możemy kpić z ostatniej klasy z Mockito. Aby być dokładnym, Mockito nie może kpić / szpiegować następujących elementów:

  • zajęcia końcowe
  • anonimowe zajęcia
  • typy prymitywne

Ale korzystanie z klasy otoki wydaje mi się dużą ceną do zapłaty, więc zamiast tego kup PowerMockito.

Yu Chen
źródło
2

Myślę, że zasadniczo musisz więcej myśleć. Zamiast tego ostatnia klasa używa jego interfejsu i fałszywego interfejsu.

Dla tego:

 public class RainOnTrees{

   fun startRain():Observable<Boolean>{

        // some code here
   }
}

Dodaj

interface iRainOnTrees{
  public void startRain():Observable<Boolean>
}

i kpię z interfejsu:

 @Before
    fun setUp() {
        rainService= Mockito.mock(iRainOnTrees::class.java)

        `when`(rainService.startRain()).thenReturn(
            just(true).delay(3, TimeUnit.SECONDS)
        )

    }
Serg Burlaka
źródło
1

Proszę spojrzeć na JMockit . Ma obszerną dokumentację z wieloma przykładami. Oto przykładowe rozwiązanie problemu (dla uproszczenia dodałem konstruktora Seasonsdo wstrzykiwania fałszywej RainOnTreesinstancji):

package jmockitexample;

import mockit.Mocked;
import mockit.Verifications;
import mockit.integration.junit4.JMockit;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(JMockit.class)
public class SeasonsTest {

    @Test
    public void shouldStartRain(@Mocked final RainOnTrees rain) {
        Seasons seasons = new Seasons(rain);

        seasons.findSeasonAndRain();

        new Verifications() {{
            rain.startRain();
        }};
    }

    public final class RainOnTrees {
        public void startRain() {
            // some code here
        }

    }

    public class Seasons {

        private final RainOnTrees rain;

        public Seasons(RainOnTrees rain) {
            this.rain = rain;
        }

        public void findSeasonAndRain() {
            rain.startRain();
        }

    }
}
Marcin
źródło
1

Rozwiązania dostarczone wspólnie przez RC i Luigi R. Viggiano są prawdopodobnie najlepszym pomysłem.

Chociaż Mockito z założenia nie może kpić z klas końcowych, podejście do delegowania jest możliwe . Ma to swoje zalety:

  1. Nie jesteś zmuszony zmienić swojej klasy na nie-końcową, jeśli takie jest zamierzenie twojego API (klasy końcowe mają swoje zalety ).
  2. Testujesz możliwość dekoracji wokół swojego interfejsu API.

W przypadku testowym celowo przekazujesz połączenia do testowanego systemu. Dlatego z założenia twoja dekoracja nic nie robi.

Dlatego test może również wykazać, że użytkownik może jedynie udekorować interfejs API zamiast go rozszerzać.

Mówiąc bardziej subiektywnie: wolę ograniczać frameworki do minimum, dlatego JUnit i Mockito są zwykle dla mnie wystarczające. W rzeczywistości ograniczenie tego sposobu czasami zmusza mnie również do refaktoryzacji na dobre.

Neel
źródło
1

Jeśli próbujesz uruchomić test jednostkowy w folderze testowym , najlepsze rozwiązanie jest w porządku. Wystarczy go dodać, dodając rozszerzenie.

Ale jeśli chcesz uruchomić go z klasą związaną z Androidem , taką jak kontekst lub aktywność, która znajduje się w folderze androidtest , odpowiedź jest dla Ciebie.

Allen
źródło
1

Dodaj te zależności, aby pomyślnie uruchomić mockito:

testImplementation 'org.mockito: mockito-core: 2.24.5'
testImplementation "org.mockito: mockito-inline: 2.24.5"

HandyPawan
źródło
0

Jak powiedzieli inni, nie będzie to działać po wyjęciu z pudełka z Mockito. Sugerowałbym użycie refleksji do ustawienia określonych pól na obiekcie używanym przez testowany kod. Jeśli często to robisz, możesz zawinąć tę funkcję w bibliotekę.

Nawiasem mówiąc, jeśli jesteś jedynym, który zalicza zajęcia do klasy, przestań to robić. Natknąłem się na to pytanie, ponieważ pracuję z interfejsem API, w którym wszystko oznaczono jako ostateczne, aby zapobiec mojej uzasadnionej potrzebie rozszerzenia (kpiny) i żałuję, że programista nie założył, że nigdy nie będę musiał rozszerzać klasy.

Tom N.
źródło
1
Publiczne klasy API powinny być otwarte na rozszerzenie. Całkowicie się zgadzam. Jednak w prywatnej bazie kodu finalpowinna być domyślna.
ErikE
0

Dla nas było tak dlatego, że wykluczyliśmy mockito-inline z testu koin. Jeden moduł stopniowy faktycznie tego potrzebował i z powodu nie powiódł się tylko w kompilacjach wersji (kompilacje debugowania w IDE działały) :-P

kenyee
źródło
0

Aby dodać klasę końcową, dodaj poniżej, aby udawać i nazwij statyczną lub niestatyczną.

1- dodaj to na poziomie klasy @SuppressStatucInitializationFor (wartość = {nazwa klasy z pakietem))
2- PowerMockito.mockStatic (classname.class) wyśmieje klasę
3, a następnie użyje instrukcji when, aby zwrócić obiekt próbny podczas wywoływania metody tej klasy.

Cieszyć się

Manas
źródło
-5

Nie próbowałem ostatecznego, ale dla prywatnego, używając odbicia usuń modyfikator działał! sprawdziłem dalej, to nie działa na ostateczne.

lwpro2
źródło
to nie odpowiada na zadane pytanie
Sanjit Kumar Mishra,