Mockito ile bir final sınıfı nasıl alay edilir


218

Son bir dersim var, şöyle bir şey:

public final class RainOnTrees{

   public void startRain(){

        // some code here
   }
}

Bu sınıfı böyle başka bir sınıfta kullanıyorum:

public class Seasons{

   RainOnTrees rain = new RainOnTrees();

   public void findSeasonAndRain(){

        rain.startRain();

    }
}

ve benim JUnit test sınıfımda sınıf Seasons.javaalay etmek istiyorum RainOnTrees. Mockito ile bunu nasıl yapabilirim?


9
Mockito buna izin vermez, ancak PowerMock buna izin verir.
fge

1
Mockito 2.x itibariyle, Mockito artık son sınıfların ve yöntemlerin alay edilmesini destekliyor.
Kent

Yanıtlar:


155

Alaycı final / statik sınıflar / yöntemler sadece Mockito v2 ile mümkündür.

bunu gradle dosyanıza ekleyin:

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

Mockito SSS bölümündeki Mockito v1 ile bu mümkün değildir :

Mockito'nun sınırlamaları nelerdir?

  • Java 1.5+ gerekir

  • Final dersleriyle alay edilemez

...


2
Bu benim için Scala'da işe yaramadı (sbt değişiklikleriyle).
micseydel

2
Bu benim için yeterli değildi. Ayrıca src / test / resources / mockito-extensions / org.mockito.plugins.MockMaker, baeldung.com/mockito-final
micseydel'e göre

204

Mockito 2 artık son sınıfları ve yöntemleri destekliyor !

Ama şimdilik bu bir "inkübasyon" özelliği. Mockito 2'deki Yenilikler bölümünde açıklanan etkinleştirmek için bazı adımlar gerektirir :

Son sınıfların ve yöntemlerin alay edilmesi, kuluçka , tercih etme özelliğidir. Bu türlerin alay edilebilirliğini sağlamak için Java aracı enstrümantasyonu ve alt sınıflarının bir kombinasyonunu kullanır. Bu, mevcut mekanizmamızdan farklı çalıştığından ve bunun farklı sınırlamaları olduğundan ve deneyim ve kullanıcı geri bildirimi toplamak istediğimizden, bu özelliğin kullanılabilir olması için açıkça etkinleştirilmesi gerekiyordu; mockito uzatma mekanizması ile src/test/resources/mockito-extensions/org.mockito.plugins.MockMakertek bir satır içeren dosya oluşturularak yapılabilir :

mock-maker-inline

Bu dosyayı oluşturduktan sonra, Mockito bu yeni motoru otomatik olarak kullanacak ve biri şunları yapabilir:

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

Sonraki aşamalarda, ekip bu özelliği kullanmanın programlı bir yolunu getirecektir. Taşınamayan tüm senaryoları belirleyip destekleyeceğiz. Bizi izlemeye devam edin ve lütfen bu özellik hakkında ne düşündüğünüzü bize bildirin!


14
Hala bir hata alıyorum: alay / casus sınıf android.content.ComponentName Mockito alay edemez / casus olamaz çünkü: - son sınıf
IgorGanapolsky

3
org.mockito.plugins.MockMakerDosyayı doğru klasöre koyduğunuzdan emin olun .
WindRider

7
Ayrıca yukarıda belirtilen sonra bile hata alıyorum: Mockito alay / casus olamaz çünkü: - final class
rcde0

8
@vCillusion bu cevap hiçbir şekilde PowerMock ile ilgili değildir.
Hat

6
Bu talimatları izledim ama hala bu işi yapamadım, başka bir şey yapmak zorunda mıydı?
Franco

43

Kendi başınıza yapamayacağınız için son sınıfta Mockito ile alay edemezsiniz.

Yaptığım şey, son sınıfı sarmak ve temsilci olarak kullanmak için final olmayan bir sınıf oluşturmaktır. Bunun bir örneği TwitterFactorysınıf ve bu benim alay edilebilir sınıfım:

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

Dezavantajı, çok sayıda kazan plakası kodunun olmasıdır; avantajı, uygulama işinizle ilgili olabilecek bazı yöntemler ekleyebilmenizdir (yukarıdaki durumda bir accessToken yerine bir kullanıcı alan getInstance gibi).

Sizin durumunuzda RainOnTrees, son sınıfa delege olan nihai olmayan bir sınıf yaratacağım . Ya da final yapamazsan daha iyi olur.


6
+1. İstenirse, @Delegateçok sayıda kazan plakasını idare etmek için Lombok's gibi bir şey kullanabilirsiniz .
ruakh

2
@luigi Junit için kod snippet'ini örnek olarak ekleyebilirsiniz. Son sınıfım için Wrapper'ı oluşturmaya çalıştım, ancak nasıl test edileceğimi bilemedim.
İnanılmaz

31

bunu gradle dosyanıza ekleyin:

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

bu mockito'nun son sınıflarla çalışmasını sağlayan bir yapılandırmadır


1
Muhtemelen şimdi "testCompile" yerine "testImplementation" kullanmalısınız. Gradle artık "testCompile" ifadesini sevmiyor.
jwehrle

harika yorum, teşekkürler! testImplementation için düzenlendi. orijinal yorum: testCompile 'org.mockito: mockito-satır içi: 2.13.0'
BennyP

2
Bu, Linux / OpenJDK 1.8 üzerinde çalışırken hatayla sonuçlanır:org.mockito.exceptions.base.MockitoInitializationException: Could not initialize inline Byte Buddy mock maker. (This mock maker is not supported on Android.)
naXa

Oracle JDK 1.8'e geçtiğinde iyi çalışıyor
naXa

23

Powermock kullanın. Bu bağlantı nasıl yapılacağını gösterir: https://github.com/jayway/powermock/wiki/MockFinal


30
Bence PowerMock sadece "reçete" üssünde çıkması gereken ilaçlardan biri gibi. Şu anlamda: PowerMock'un çok fazla sorunu olduğu açıkça anlaşılmalıdır ; ve onu kullanmak son çare gibidir; ve mümkün olduğunca kaçınılmalıdır.
GhostCat

1
Neden öyle diyorsun?
PragmaticProgrammer

PowermockResmi olarak kontrol edilen kapsamımı artırmak için son sınıfları ve statik yöntemleri alay etmek için kullanıyordum Sonarqube. SonarQube'den beri kapsama alanı% 0'dı, çünkü herhangi bir nedenle Powermock kullanan sınıfları tanımıyor. Ben ve benim takım çevrimiçi bazı iplik bunu gerçekleştirmek için oldukça zaman aldı. Bu, Powermock'a dikkat etmenin ve muhtemelen kullanmamanın nedenlerinden sadece bir tanesidir.
amer

16

Sadece takip etmek. Lütfen bu satırı not dosyasına ekleyin:

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

Mockito-core ve mockito-all'ın çeşitli versiyonlarını denedim. İkisi de çalışmıyor.


1
Buna ek olarak, gözlemlediğim bir şey, Powermock'u mockito ile birlikte kullanıyorsanız; daha sonra 'src / test / resources / mockito-extensions / org.mockito.plugins.MockMaker' içine mockmaker eklenti dosyasını eklemek son sınıfların alay edilmesinde yararlı olmaz. Bunun yerine, yukarıdaki Michael_Zhang tarafından belirtildiği gibi bir bağımlılık eklemek, son sınıfların alay konusu konusunu çözecektir. Ayrıca, Mockito1 yerine Mockito 2 kullandığınızdan emin olun
05'te dishooom

12

Sanırım başardınız finalçünkü diğer sınıfların uzamasını engellemek istiyorsunuz RainOnTrees. As Etkili Java anlaşılacağı (öğe 15), bunu yapmadan uzatılması için bir sınıf yanından ayırmak için başka bir yol var final:

  1. finalAnahtar kelimeyi kaldırın ;

  2. Yapıcı yap private. Hiçbir sınıf yapıcıyı çağıramayacağı için onu genişletemez super;

  3. Sınıfınızı örneklemek için statik bir fabrika yöntemi oluşturun.

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

Bu stratejiyi kullanarak Mockito'yu kullanabilir ve sınıfınızı küçük bir kaynak kodu ile genişletme için kapalı tutabilirsiniz.


1
bu mockito 2 ile alay edilebilecek son yöntemler için işe yaramaz.
asukasz Rzeszotarski

11

Ben de aynı problemi yaşadım. Alay etmeye çalıştığım sınıf basit bir sınıf olduğundan, bunun bir örneğini oluşturdum ve geri verdim.


2
Kesinlikle, neden basit bir sınıfla alay ediyorsun? Alay etmek 'pahalı' etkileşimler içindir: diğer hizmetler, motorlar, veri sınıfları vb.
StripLight

3
Bunun bir örneğini oluşturursanız, daha sonra Mockito.verify yöntemlerini uygulayamazsınız. Alayların ana kullanımı, bazı yöntemlerini test edebilmektir.
riroo

6

Bunu deneyin:

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

Benim için çalıştı. "SomeMockableType.class", sahte veya casusluk yapmak istediğiniz öğenin üst sınıfıdır ve someInstanceThatIsNotMockableOrSpyable, sahte veya casusluk yapmak istediğiniz gerçek sınıftır.

Daha fazla ayrıntı için buraya bir göz atın


3
Delegelerin yerel casus alaycılığından çok farklı olduğuna dikkat edilmelidir. Yerel bir mockito casusunda, casusun kendisine referansta "this" (çünkü alt sınıf kullan) Casus değil. Bu nedenle, kendi kendine arama işlevleri için geri dönme / doğrulama yapmanın bir yolu yoktur.
Dennis C

1
bir örnek verebilir misin?
Vishwa Ratna

5

Bazı durumlarda uygulanabilecek başka bir geçici çözüm, bu son sınıf tarafından uygulanan bir arabirim oluşturmak, somut sınıf yerine arabirimi kullanmak için kodu değiştirmek ve sonra arabirimi alay etmektir. Bu, sözleşmeyi (arayüz) uygulamadan (son sınıf) ayırmanıza olanak tanır. Tabii ki, istediğiniz şey gerçekten son sınıfa bağlanmaksa, bu geçerli olmayacaktır.


5

Aslında casusluk için kullandığım bir yol var. Sadece iki ön koşul yerine getirildiğinde sizin için işe yarar:

  1. Son sınıf örneğini enjekte etmek için bir çeşit DI kullanırsınız
  2. Son sınıf bir arayüz uygular

Lütfen Etkili Java'dan 16. Öğeyi geri çağırın . Bir sarıcı (son değil) oluşturabilir ve tüm aramaları son sınıf örneğine yönlendirebilirsiniz:

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

Şimdi sadece son sınıfınızı alay etmekle kalmaz, aynı zamanda casusluk yapabilirsiniz:

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

5

Mockito 3 ve daha fazlasında aynı problemim var ve bu bağlantıdan sabitledim

Mockito ile aşağıdaki gibi son sınıfları ve yöntemleri alay et

Mockito'nun son sınıfları ve yöntemleri alay etmek için kullanılabilmesi için önce yapılandırılması gerekir.

Projenin src / test / resources / mockito-extensions dizinine org.mockito.plugins.MockMaker adlı bir metin dosyası eklememiz ve tek bir metin satırı eklememiz gerekir:

mock-maker-inline

Mockito, uzantılar dizinini yüklendiğinde yapılandırma dosyaları için kontrol eder. Bu dosya, son yöntem ve sınıfların alay edilmesini sağlar.


4

Android + Kotlin'de aynı sorunla (Mockito + Final Class) karşılaşanlar için zaman tasarrufu. Kotlin'de olduğu gibi sınıflar varsayılan olarak kesindir. Mimari bileşenli Google Android örneklerinden birinde bir çözüm buldum. Buradan seçilen çözüm: https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample

Aşağıdaki ek açıklamaları oluşturun:

/**
 * 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

Gradle dosyanızı değiştirin. Buradan örnek alın: 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'
}

Şimdi herhangi bir sınıfa test için açık hale getirebilirsiniz:

@OpenForTesting
class RepoRepository 

Bu uygulama düzeyinde build.gradle iyi çalışır ama bunu kütüphane düzeyinde almak için ne yapabiliriz?
Sumit T

Biraz detaylandırabilir misin? Genellikle, kütüphanelere bağlanmak için cephe desenini kullanın. Ve uygulamayı test etmek için bu cephe sınıfları alay. Bu şekilde herhangi bir lib sınıfıyla alay etmemize gerek kalmaz.
Ozeetee

3

Bu, son sınıflar ve yöntemlerin alay edilmesini destekleyen yeni inkübasyon özelliğiyle Mockito2'yi kullanıyorsanız yapılabilir.

Dikkat edilmesi gereken önemli noktalar:
1. “org.mockito.plugins.MockMaker” adıyla basit bir dosya oluşturun ve “mockito-extensions” adlı bir klasöre yerleştirin. Bu klasör sınıf yolunda kullanılabilir olmalıdır.
2. Yukarıda oluşturulan dosyanın içeriği aşağıda belirtildiği gibi tek bir satır olmalıdır:
mock-maker-inline

Mockito uzatma mekanizmasını etkinleştirmek ve bu tercih özelliğini kullanmak için yukarıdaki iki adım gereklidir.

Örnek sınıflar aşağıdaki gibidir:

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

}

}

Umarım yardımcı olur.

Mock-a-mockable burada mevcut tam makale .


Cevabı buraya eklemeli ve harici bir siteye link vermemelisiniz. Prosedür uzunsa bir genel bakış ekleyebilirsiniz.
rghome

@RunWith (PowerMockRunner.class) @PrepareForTest ({AFinalClass.class}) ile alay ederken aşağıdaki ek açıklamaların kullanıldığından emin olun
vCillusion

1
@vCillusion - Gösterdiğim örnek sadece Mockito2 API'sini kullanıyor.Mockito2'nin katılım özelliğini kullanarak, son sınıfları Powermock kullanmaya gerek kalmadan doğrudan alay edebilirsiniz.
ksl

2

Evet aynı sorun burada, Mockito ile son bir sınıfta alay edemeyiz. Doğru olmak gerekirse, Mockito aşağıdakileri alay edemez / alamaz:

  • final dersleri
  • anonim sınıflar
  • ilkel tipler

Ama bir sarıcı sınıf kullanmak bana büyük bir bedel gibi görünüyor, bu yüzden bunun yerine PowerMockito alın.


2

Bence prensipte daha fazla düşünmelisin. Bunun yerine son sınıf onun arayüzü ve sahte arayüzü kullanın.

Bunun için:

 public class RainOnTrees{

   fun startRain():Observable<Boolean>{

        // some code here
   }
}

Ekle

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

ve sahte arayüz:

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

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

    }

1

Lütfen JMockit'e bakın . Çok sayıda örnekle kapsamlı belgelere sahiptir. Burada sorununuza örnek bir çözüm var (basitleştirmek için yapıcı örneği Seasonsenjekte etmek için yapıcı ekledim RainOnTrees):

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

    }
}

1

RC ve Luigi R. Viggiano tarafından sağlanan çözümler muhtemelen en iyi fikirdir.

Mockito , tasarım gereği, son sınıflarla alay edemese de , delegasyon yaklaşımı mümkündür . Bunun avantajları vardır:

  1. API'nızın ilk etapta bunu yapması gerekiyorsa (final sınıflarının faydaları vardır ) sınıfınızı final dışı olarak değiştirmek zorunda değilsiniz .
  2. API'nizin etrafında bir dekorasyon olasılığını test ediyorsunuz.

Test durumunuzda, çağrıları kasıtlı olarak test edilen sisteme yönlendirirsiniz. Bu nedenle, tasarım gereği, senin dekorasyon gelmez değil bir şey yapmak.

Bu nedenle test ederseniz, kullanıcının API'yı genişletmek yerine yalnızca dekore edebileceğini de gösterebilirsiniz.

Daha öznel bir notta: Çerçeveleri minimumda tutmayı tercih ediyorum, bu yüzden JUnit ve Mockito genellikle benim için yeterli. Aslında, bu şekilde kısıtlamak bazen beni de iyilik için yeniden düzenlemeye zorlar.


1

Test klasörü altında birim testi yapmaya çalışıyorsanız , en iyi çözüm iyidir. Sadece bir uzantı ekleyerek izleyin.

Ancak , androidtest klasörü altında bağlam veya etkinlik gibi android ile ilgili sınıfla çalıştırmak istiyorsanız , cevap sizin için.


1

Mockito komutunu başarıyla çalıştırmak için bu bağımlılıkları ekleyin:

testImplementation 'org.mockito: mockito-çekirdek: 2.24.5'
testImplementation "org.mockito: mockito-inline: 2.24.5"


0

Diğerlerinin de belirttiği gibi, bu Mockito ile kutudan çıkmayacak. Test altındaki kod tarafından kullanılan nesne üzerinde belirli alanları ayarlamak için yansıma kullanarak öneririz. Kendinizi bunu çok fazla yaparsanız, bu işlevselliği bir kütüphaneye sarabilirsiniz.

Bir kenara, eğer final sınıfını işaretleyen sizseniz, bunu yapmayı bırakın. Bu soruya rastladım çünkü meşru uzatma ihtiyacımı önlemek için her şeyin nihai olarak işaretlendiği bir API ile çalışıyorum ve geliştiricinin sınıfı asla genişletmem gerektiğini varsaymamıştı.


1
Genel API sınıfları uzantıya açık olmalıdır. Tamamen katılıyorum. Ancak, özel bir kod tabanındaki finalvarsayılan değer olmalıdır.
ErikE

0

Bizim için mockito-inline'ı koin testinden hariç tutmamızdı. Bir sınıf modülü aslında buna ihtiyaç duyuyordu ve bu nedenle yalnızca sürüm derlemelerinde başarısız oldu (IDE'de hata ayıklama derlemeleri çalıştı) :-P


0

Son sınıf için mock ve statik veya statik olmayan çağırmak için aşağı ekleyin.

1- bunu sınıf düzeyinde ekleyin @SuppressStatucInitializationFor (value = {paket ile sınıf adı})
2- PowerMockito.mockStatic (classname.class) sınıf
3- ile alay eder, ardından bu sınıfın yöntemini çağırırken sahte nesneyi döndürmek için when ifadenizi kullanır.

Zevk almak


-5

Final deneyin vermedi, ama özel, yansıma kullanarak değiştirici kaldırmak çalıştı! daha fazla kontrol ettiyseniz, final için işe yaramaz.


bu sorulan soruya cevap vermiyor
Sanjit Kumar Mishra
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.