Sahte nesneleri başlatma - MockIto


122

MockIto kullanarak sahte bir nesneyi başlatmanın birçok yolu vardır. Bunların arasında en iyi yol nedir?

1.

 public class SampleBaseTestCase {

   @Before public void initMocks() {
       MockitoAnnotations.initMocks(this);
   }

2.

@RunWith(MockitoJUnitRunner.class)

[DÜZENLE] 3.

mock(XXX.class);

bunlardan daha iyi başka yollar varsa bana öner ...

Yanıtlar:


153

Örneklerin başlatılması için , koşucu veya MockitoAnnotations.initMockskullanımı kesinlikle eşdeğer çözümlerdir. Bir javadoc itibaren MockitoJUnitRunner :

JUnit 4.5 runner initializes mocks annotated with Mock, so that explicit usage of MockitoAnnotations.initMocks(Object) is not necessary. Mocks are initialized before each test method.


İlk çözüm (ile MockitoAnnotations.initMocks) belirli bir koşucuyu önceden yapılandırdığınızda kullanılabilir (SpringJUnit4ClassRunner , test durumunuzda örneğin) .

İkinci çözüm (ile MockitoJUnitRunner) daha klasik ve benim favorim. Kod daha basit. Bir kaçak büyük avantajı sağlar kullanarak çerçeve kullanım otomatik doğrulama ile (tarif edildiği @ David Wallace bölgesindeki Bu yanıt ).

Her iki çözüm de test yöntemleri arasında taklitleri (ve casusları) paylaşmaya izin verir. İle birleştiğinde @InjectMocks, birim testlerinin çok hızlı bir şekilde yazılmasına izin verirler. Klişe alay kodu azaltılır, testlerin okunması daha kolaydır. Örneğin:

@RunWith(MockitoJUnitRunner.class)
public class ArticleManagerTest {

    @Mock private ArticleCalculator calculator;
    @Mock(name = "database") private ArticleDatabase dbMock;
    @Spy private UserProvider userProvider = new ConsumerUserProvider();

    @InjectMocks private ArticleManager manager;

    @Test public void shouldDoSomething() {
        manager.initiateArticle();
        verify(database).addListener(any(ArticleListener.class));
    }

    @Test public void shouldDoSomethingElse() {
        manager.finishArticle();
        verify(database).removeListener(any(ArticleListener.class));
    }
}

Artıları: Kod minimum düzeydedir

Eksileri: Kara büyü. IMO, esas olarak @InjectMocks ek açıklamasından kaynaklanmaktadır. Bu ek açıklamayla "kodun acısını kaybedersiniz" ( @Brice'ın harika yorumlarına bakın )


Üçüncü çözüm, her test yönteminde kendi modelinizi oluşturmaktır. @Mlk tarafından yanıtında açıklandığı gibi " kendi kendine yeten test " e sahip olmasına izin verir .

public class ArticleManagerTest {

    @Test public void shouldDoSomething() {
        // given
        ArticleCalculator calculator = mock(ArticleCalculator.class);
        ArticleDatabase database = mock(ArticleDatabase.class);
        UserProvider userProvider = spy(new ConsumerUserProvider());
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.initiateArticle();

        // then 
        verify(database).addListener(any(ArticleListener.class));
    }

    @Test public void shouldDoSomethingElse() {
        // given
        ArticleCalculator calculator = mock(ArticleCalculator.class);
        ArticleDatabase database = mock(ArticleDatabase.class);
        UserProvider userProvider = spy(new ConsumerUserProvider());
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.finishArticle();

        // then
        verify(database).removeListener(any(ArticleListener.class));
    }
}

Artıları: API'nizin nasıl çalıştığını açıkça gösteriyorsunuz (BDD ...)

Eksileri: daha fazla standart kod var. (Sahte yaratma)


Benim tavsiyem bir uzlaşmadır. @MockEk açıklamayı ile kullanın @RunWith(MockitoJUnitRunner.class), ancak @InjectMocksşunları kullanmayın :

@RunWith(MockitoJUnitRunner.class)
public class ArticleManagerTest {

    @Mock private ArticleCalculator calculator;
    @Mock private ArticleDatabase database;
    @Spy private UserProvider userProvider = new ConsumerUserProvider();

    @Test public void shouldDoSomething() {
        // given
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.initiateArticle();

        // then 
        verify(database).addListener(any(ArticleListener.class));
    }

    @Test public void shouldDoSomethingElse() {
        // given
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.finishArticle();

        // then 
        verify(database).removeListener(any(ArticleListener.class));
    }
}

Artıları: API'nizin nasıl çalıştığını açıkça gösterirsiniz (Nasıl ArticleManagerörneklenir). Standart kod yok.

Eksileri: Test bağımsız değildir, daha az kod zahmeti


Yine de dikkatli olun, ek açıklamalar kullanışlıdır, ancak sizi kötü OO tasarımına (veya onu düşürmeye) karşı korumazlar. Kişisel olarak, standart kodu azaltmaktan mutlu olsam da, tasarımı daha iyi bir tasarımla değiştirmeyi tetikleyen kodun (veya PITA'nın) acısını kaybediyorum, bu yüzden ben ve ekip OO tasarımına dikkat ediyoruz. SOLID tasarımı veya GOOS fikirleri gibi ilkelerle OO tasarımını takip etmenin, alayların nasıl başlatılacağını seçmekten çok daha önemli olduğunu düşünüyorum.
Brice

1
(devam edin) Bu nesnenin nasıl oluşturulduğunu görmezseniz, bununla ilgili acıyı hissetmezsiniz ve yeni işlevler eklenmesi gerekirse gelecekteki programcılar iyi tepki vermeyebilir. Her neyse, bu her iki şekilde de tartışılabilir, sadece dikkatli olun diyorum.
Brice

6
Bu ikisinin eşdeğer olması DOĞRU DEĞİLDİR. Daha basit kodun kullanmanın tek avantajı olduğu DOĞRU DEĞİLDİR MockitoJUnitRunner. Farklılıklar hakkında daha fazla bilgi için, stackoverflow.com/questions/10806345/… adresindeki soruya ve benim yanıtıma bakın.
Dawood ibn Kareem

2
@Gontard Evet emin bağımlılıklar görünür, ancak bu yaklaşımı kullanırken kodun yanlış gittiğini gördüm. Kullanma hakkında Collaborator collab = mock(Collaborator.class)Bence, bu şekilde kesinlikle geçerli bir yaklaşımdır. Bu ayrıntılı olma eğiliminde olsa da, testlerin anlaşılabilirliği ve yeniden düzenlenebilirliği konusunda kazanabilirsiniz. Her iki yolun da artıları ve eksileri var, hangi yaklaşımın daha iyi olduğuna henüz karar vermedim. Amyway saçmalık yazmak her zaman mümkündür ve muhtemelen bağlama ve kodlayıcıya bağlıdır.
Brice

1
@mlk sana tamamen katılıyorum. İngilizcem çok iyi değil ve nüansları yok. Benim amacım UNIT kelimesinde ısrar etmekti.
gontard

30

Bir JUnit4 kullandığı örneğini mocks, dördüncü bir yolu (v1.10.7 itibariyle) şimdi vardır kuralı denir MockitoRule .

@RunWith(JUnit4.class)   // or a different runner of your choice
public class YourTest
  @Rule public MockitoRule rule = MockitoJUnit.rule();
  @Mock public YourMock yourMock;

  @Test public void yourTestMethod() { /* ... */ }
}

JUnit , @ Kural ile açıklanmış TestRule alt sınıflarını arar ve bunları Çalıştırıcının sağladığı test İfadelerini sarmak için kullanır . Bunun sonucu, @Before yöntemlerini, yöntemlerden sonra @ yöntemlerini ayıklayabilmeniz ve hatta ... sarmalayıcıları kurallara göre yakalamayı deneyebilmenizdir. Bunlarla testinizin içinden ExpectedException'ın yaptığı gibi etkileşime bile girebilirsiniz. yaptığı .

MockitoRule , Parameterized gibi başka herhangi bir koşucu kullanabilmeniz dışında, neredeyse tam olarak MockitoJUnitRunner gibi davranır. (test oluşturucularınızın bağımsız değişkenler almasına izin verir, böylece testlerinizin birden çok kez çalıştırılmasına izin verir) veya Robolectric'in test çalıştırıcısı (böylece sınıf yükleyicisi Java alternatifleri sağlayabilir) Android yerel sınıfları için). Bu, son JUnit ve Mockito sürümlerinde kullanımı kesinlikle daha esnek hale getirir.

Özetle:

  • Mockito.mock(): Ek açıklama desteği veya kullanım doğrulaması olmadan doğrudan çağrı.
  • MockitoAnnotations.initMocks(this): Ek açıklama desteği, kullanım doğrulaması yok.
  • MockitoJUnitRunner: Ek açıklama desteği ve kullanım doğrulaması, ancak bu koşucuyu kullanmalısınız.
  • MockitoRule: Ek açıklama desteği ve herhangi bir JUnit çalıştırıcısı ile kullanım doğrulama.

Ayrıca bakınız: JUnit @Rule nasıl çalışır?


3
@get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
Kotlin'de

10

Bunu yapmanın güzel bir yolu var.

  • Birim Testi ise şunu yapabilirsiniz:

    @RunWith(MockitoJUnitRunner.class)
    public class MyUnitTest {
    
        @Mock
        private MyFirstMock myFirstMock;
    
        @Mock
        private MySecondMock mySecondMock;
    
        @Spy
        private MySpiedClass mySpiedClass = new MySpiedClass();
    
        // It's gonna inject the 2 mocks and the spied object per reflection to this object
        // The java doc of @InjectMocks explains it really well how and when it does the injection
        @InjectMocks
        private MyClassToTest myClassToTest;
    
        @Test
        public void testSomething() {
        }
    }
  • DÜZENLEME: Bir Entegrasyon testiyse, bunu yapabilirsiniz (Spring ile bu şekilde kullanılması amaçlanmamıştır. Sadece farklı Koşucularla taklitleri başlatabileceğinizi gösterin):

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("aplicationContext.xml")
    public class MyIntegrationTest {
    
        @Mock
        private MyFirstMock myFirstMock;
    
        @Mock
        private MySecondMock mySecondMock;
    
        @Spy
        private MySpiedClass mySpiedClass = new MySpiedClass();
    
        // It's gonna inject the 2 mocks and the spied object per reflection to this object
        // The java doc of @InjectMocks explains it really well how and when it does the injection
        @InjectMocks
        private MyClassToTest myClassToTest;
    
        @Before
        public void setUp() throws Exception {
              MockitoAnnotations.initMocks(this);
        }
    
        @Test
        public void testSomething() {
        }
    }

1
MOCK, Entegrasyon testlerine de dahilse, mantıklı olur mu?
VinayVeluri

2
aslında olmayacak, senin hakkın. Sadece Mockito'nun olanaklarını göstermek istedim. Örneğin, RESTFuse kullanıyorsanız, MockitoAnnotations.initMocks (this) ile alayları başlatabilmeniz için onların koşucusunu kullanmanız gerekir;
emd

8

MockitoAnnotations & runner yukarıda iyi tartışıldı, bu yüzden sevilmeyenler için paramı atacağım:

XXX mockedXxx = mock(XXX.class);

Bunu kullanıyorum çünkü onu biraz daha açıklayıcı buluyorum ve testlerimin (olabildiğince) bağımsız olmasını istediğim için üye değişkenleri kullanmamayı tercih ediyorum (doğru yasaklama değil).


Sahte (XX.class) kullanmaya göre test senaryosunun kendi kendine yetmesi dışında başka bir avantajı var mı?
VinayVeluri

Bildiğim kadarıyla değil.
Michael Lloyd Lee mlk

3
Testi okumak için anlamak için daha az sihir. Değişkeni bildirirsiniz ve ona bir değer verirsiniz - açıklama, yansıma vb. Yok
Karu

2

JUnit 5 Jupiter için küçük bir örnek, "RunWith" kaldırıldı, şimdi "@ExtendWith" Ek Açıklamasını kullanarak Uzantıları kullanmanız gerekiyor.

@ExtendWith(MockitoExtension.class)
class FooTest {

  @InjectMocks
  ClassUnderTest test = new ClassUnderTest();

  @Spy
  SomeInject bla = new SomeInject();
}

1

Diğer cevaplar harika ve isterseniz / ihtiyacınız olursa daha fazla ayrıntı içeriyor.
Bunlara ek olarak bir TL eklemek istiyorum; DR:

  1. Kullanmayı tercih et
    • @RunWith(MockitoJUnitRunner.class)
  2. Yapamıyorsanız (zaten farklı bir koşucu kullandığınız için), kullanmayı tercih edin
    • @Rule public MockitoRule rule = MockitoJUnit.rule();
  3. (2) 'ye benzer, ancak artık bunu kullanmamalısınız:
    • @Before public void initMocks() { MockitoAnnotations.initMocks(this); }
  4. Testlerden yalnızca birinde bir model kullanmak istiyorsanız ve aynı test sınıfındaki diğer testlere maruz bırakmak istemiyorsanız, şunu kullanın:
    • X x = mock(X.class)

(1) ve (2) ve (3) birbirini dışlar.
(4) diğerleri ile kombinasyon halinde kullanılabilir.

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.