Mockito: Bir yöntem içinde oluşturulan bir nesnede yöntemin çağrılması nasıl yapılır?


322

Mockito'da yeniyim.

Aşağıdaki sınıf göz önüne alındığında, someMethodtam olarak bir kez fooçağrıldıktan sonra çağrıldığını doğrulamak için Mockito'yu nasıl kullanabilirim ?

public class Foo
{
    public void foo(){
        Bar bar = new Bar();
        bar.someMethod();
    }
}

Aşağıdaki doğrulama çağrısını yapmak istiyorum,

verify(bar, times(1)).someMethod();

burada barbir alay örneğidir Bar.


2
stackoverflow.com/questions/6520242/… - Ama PowerMock'u kullanmak istemiyorum.
mre

API veya PowerMock'u değiştirin. İkinin biri.
John B

Böyle bir şey nasıl örtülür? ortak senkronize geçersiz başlangıç ​​(BundleContext bundleContext) Exception atar {BundleContext bc = bundleContext; logger.info ("HTTP SERVICE BUNDLE BAŞLANGIÇ"); this.tracker = yeni ServiceTracker (bc, HttpService.class.getName (), null) {@Override public Object addService (ServiceReference serviceRef) {httpService = (HttpService) super.addingService (serviceRef); registerServlets (); dönüş httpService; }}}
ShAkKiR

Yanıtlar:


366

Bağımlılık Enjeksiyonu

Bar örneğini veya Bar örneğini (veya bunu yapmanın diğer 483 yolundan birini) oluşturmak için kullanılan bir fabrikayı enjekte ederseniz, testi gerçekleştirmek için gereken erişiminiz olur.

Fabrika Örnek:

Böyle bir Foo sınıfı verildiğinde:

public class Foo {
  private BarFactory barFactory;

  public Foo(BarFactory factory) {
    this.barFactory = factory;
  }

  public void foo() {
    Bar bar = this.barFactory.createBar();
    bar.someMethod();
  }
}

Test yönteminizde böyle bir BarFactory enjekte edebilirsiniz:

@Test
public void testDoFoo() {
  Bar bar = mock(Bar.class);
  BarFactory myFactory = new BarFactory() {
    public Bar createBar() { return bar;}
  };

  Foo foo = new Foo(myFactory);
  foo.foo();

  verify(bar, times(1)).someMethod();
}

Bonus: Bu, TDD'nin kodunuzun tasarımını nasıl yönlendirebileceğinin bir örneğidir.


6
Birim testi için sınıfı değiştirmeden bunu yapmanın bir yolu var mı?
mre

6
Bar bar = mock(Bar.class)yerineBar bar = new Bar();
John B

7
farkında değilim. ancak, yalnızca birim sınama için sınıfı değiştirmenizi önermiyorum. Bu gerçekten temiz kod ve SRP hakkında bir sohbet. Veya .. Bar nesnesi oluşturmak için Foo sınıfındaki foo () yönteminin sorumluluğu. Cevap evet ise, bu bir uygulama detayıdır ve etkileşimi özellikle test etme konusunda endişelenmemelisiniz (@ Michael'ın cevabına bakınız). Cevabınız hayırsa, sınıfı değiştiriyorsunuz çünkü testteki zorluğunuz tasarımınızın biraz iyileştirilmesi gereken kırmızı bir bayrak (bu yüzden eklediğim bonus TDD'nin tasarımı nasıl yönlendirdiğini).
csturtz

3
Mockito'nun "doğrulamasına" "gerçek" bir nesne iletebilir misiniz?
John B

4
Ayrıca fabrikayı alay edebilirsiniz: BarFactory myFactory = mock(BarFactory.class); when(myFactory.createBar()).thenReturn(bar);
levsa

18

Klasik yanıt, "Yapmazsın." FooDahili API'sini değil , genel API'sını test edersiniz .

FooEtkilenen nesnenin (veya ortamdaki diğer nesnelerin daha az iyi) davranışları var mı foo()? Varsa, test edin. Ve değilse, yöntem ne yapar?


4
Peki burada gerçekten ne test edersiniz? Kamu API FooDİR public void foo()internals edilir sadece ilgili bar.
behelit

15
Testlere ihtiyaç duyan yan etkileri olan gerçek hatalar oluşana kadar yalnızca herkese açık API'yı test etmek iyidir. Eğer özel yöntem olduğunu keşfetmek kadar Örneğin, özel yöntem düzgün bir HTTP bağlantılarını kapatıyor denetimi olduğunu overkill değil düzgün olan bağlantılarını kapatan ve böylece büyük bir soruna neden oluyor. Bu noktada Mockito ve verify()entegrasyon testinin kutsal sunağına ibadet etmiyor olsanız bile gerçekten çok yardımcı oluyor.
Dawngerpony

@DuffJ Java kullanmıyorum, ancak derleyicinizin veya kod analizi aracınızın algılaması gereken bir şey gibi geliyor.
user247702

3
DuffJ ile hemfikirim, fonksiyonel programlama eğlenceli olsa da, kodunuzun dış dünyayla etkileşime girdiği bir nokta var. Buna "dahili", "yan etkiler" veya "işlevsellik" adını vermek önemli değildir, kesinlikle bu etkileşimi test etmek istersiniz: gerçekleşirse ve doğru sayıda ve doğru argümanlarla gerçekleşirse. @Stijn: bu kötü bir örnek olabilir (ancak birden fazla bağlantı açılmalı ve sadece bazıları kapatıldıysa, ilginçleşiyorsa). Daha iyi bir örnek, bağlantı üzerinden doğru verilerin gönderilip gönderilmeyeceğini hava durumunu kontrol etmek olabilir.
Andras Balázs Lajtha

13

DI veya Fabrikalar kullanmak istemiyorsanız. Sınıfınızı biraz zor bir şekilde yeniden düzenleyebilirsiniz:

public class Foo {
    private Bar bar;

    public void foo(Bar bar){
        this.bar = (bar != null) ? bar : new Bar();
        bar.someMethod();
        this.bar = null;  // for simulating local scope
    }
}

Ve test sınıfınız:

@RunWith(MockitoJUnitRunner.class)
public class FooTest {
    @Mock Bar barMock;
    Foo foo;

    @Test
    public void testFoo() {
       foo = new Foo();
       foo.foo(barMock);
       verify(barMock, times(1)).someMethod();
    }
}

Daha sonra foo yönteminizi çağıran sınıf bunu şu şekilde yapacaktır:

public class thirdClass {

   public void someOtherMethod() {
      Foo myFoo = new Foo();
      myFoo.foo(null);
   }
}

Yöntemi bu şekilde çağırırken görebileceğiniz gibi, Bar sınıfını, istediğiniz bir şey olan foo yönteminizi çağıran başka bir sınıfa aktarmanız gerekmez.

Tabii ki dezavantajı, arayanın Çubuk Nesnesini ayarlamasına izin vermenizdir.

Umarım yardımcı olur.


3
Bence bu bir anti-desen. Bağımlılıklar enjekte edilmelidir, süre. İsteğe bağlı olarak enjekte edilen bir bağımlılığın yalnızca test amacıyla kullanılmasına izin vermek, kodun geliştirilmesinden kasıtlı olarak kaçınıyor ve üretimde çalışan koddan farklı bir şeyi kasıtlı olarak test ediyor. Her ikisi de korkunç, korkunç şeyler.
ErikE

8

Örnek kodunuz için çözüm PowerMockito.whenNew

  • mockito-tüm 1.10.8
  • powermock-çekirdek 1.6.1
  • powermock-modül-junit4 1.6.1
  • powermock-api-mockito 1.6.1
  • haziran 4.12

FooTest.java

package foo;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

//Both @PrepareForTest and @RunWith are needed for `whenNew` to work 
@RunWith(PowerMockRunner.class)
@PrepareForTest({ Foo.class })
public class FooTest {

    // Class Under Test
    Foo cut;

    @Mock
    Bar barMock;

    @Before
    public void setUp() throws Exception {
        cut = new Foo();

    }

    @After
    public void tearDown() {
        cut = null;

    }

    @Test
    public void testFoo() throws Exception {

        // Setup
        PowerMockito.whenNew(Bar.class).withNoArguments()
                .thenReturn(this.barMock);

        // Test
        cut.foo();

        // Validations
        Mockito.verify(this.barMock, Mockito.times(1)).someMethod();

    }

}

JUnit Çıkışı JUnit Çıkışı


8

Sanırım Mockito @InjectMocksbu iş için bir yol.

Niyetinize bağlı olarak şunları kullanabilirsiniz:

  1. Yapıcı enjeksiyonu
  2. Özellik ayarlayıcı enjeksiyonu
  3. Alan enjeksiyonu

Dokümanlar'da daha fazla bilgi

Alan enjeksiyonlu bir örnek aşağıdadır:

Sınıflar:

public class Foo
{
    private Bar bar = new Bar();

    public void foo() 
    {
        bar.someMethod();
    }
}

public class Bar
{
    public void someMethod()
    {
         //something
    }
}

Ölçek:

@RunWith(MockitoJUnitRunner.class)
public class FooTest
{
    @Mock
    Bar bar;

    @InjectMocks
    Foo foo;

    @Test
    public void FooTest()
    {
        doNothing().when( bar ).someMethod();
        foo.foo();
        verify(bar, times(1)).someMethod();
    }
}

3

Evet, gerçekten yapmak / yapmanız gerekiyorsa PowerMock'u kullanabilirsiniz. Bu son çare olarak kabul edilmelidir. PowerMock ile çağrıyı yapıcıya bir sahte geri döndürmesine neden olabilirsiniz. Sonra sahte doğrulayın. Bununla birlikte, csturtz "doğru" cevaptır.

İşte yeni nesnelerin sahte yapımına bağlantı


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.