Mockito: bagaimana cara memverifikasi metode dipanggil pada objek yang dibuat dalam suatu metode?

322

Saya baru mengenal Mockito.

Dengan kelas di bawah ini, bagaimana saya bisa menggunakan Mockito untuk memverifikasi yang someMethoddipanggil tepat satu kali setelah foodipanggil?

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

Saya ingin melakukan panggilan verifikasi berikut,

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

di mana barcontoh mengejek Bar.

mre
sumber
2
stackoverflow.com/questions/6520242/… - Tapi saya tidak ingin menggunakan PowerMock.
mre
Ubah API atau PowerMock. Salah satu dari keduanya.
John B
Bagaimana cara meliput sesuatu seperti ini ?? mulai batal tersinkronisasi publik (BundleContext bundleContext) melempar Exception {BundleContext bc = bundleContext; logger.info ("MULAI BUNDLE LAYANAN HTTP"); this.tracker = ServiceTracker baru (bc, HttpService.class.getName (), null) {@Override Object publik MenambahkanService (ServiceReference serviceRef) {httpService = (HttpService) super.addingService (serviceRef); registerServlets (); kembalikan httpService; }}}
ShAkKiR

Jawaban:

366

Injeksi Ketergantungan

Jika Anda menyuntikkan turunan Bar, atau pabrik yang digunakan untuk membuat turunan Bar (atau salah satu dari 483 cara lain untuk melakukan ini), Anda akan memiliki akses yang diperlukan untuk melakukan pengujian.

Contoh Pabrik:

Diberi kelas Foo yang ditulis seperti ini:

public class Foo {
  private BarFactory barFactory;

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

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

dalam metode pengujian Anda, Anda dapat menyuntikkan BarFactory seperti ini:

@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: Ini adalah contoh bagaimana TDD dapat mendorong desain kode Anda.

csturtz
sumber
6
Apakah ada cara untuk melakukan ini tanpa memodifikasi kelas untuk pengujian unit?
mre
6
Bar bar = mock(Bar.class)bukannyaBar bar = new Bar();
John B
7
Bukannya aku sadar. tapi, saya tidak menyarankan Anda memodifikasi kelas hanya untuk pengujian unit. Ini benar-benar percakapan tentang kode bersih dan SRP. Atau .. apakah itu tanggung jawab metode foo () di kelas Foo untuk membangun objek Bar. Jika jawabannya adalah ya, maka ini merupakan detail implementasi dan Anda tidak perlu khawatir untuk menguji interaksi secara khusus (lihat jawaban @ Michael). Jika jawabannya tidak, maka Anda memodifikasi kelas karena kesulitan Anda dalam pengujian adalah tanda merah bahwa desain Anda perlu sedikit perbaikan (maka bonus yang saya tambahkan adalah bagaimana TDD menggerakkan desain).
csturtz
3
Bisakah Anda meneruskan objek "nyata" ke "verifikasi" Mockito?
John B
4
Anda juga dapat mengejek pabrik: BarFactory myFactory = mock(BarFactory.class); when(myFactory.createBar()).thenReturn(bar);
levsa
18

Respons klasiknya adalah, "Kamu tidak." Anda menguji API publik Foo, bukan internal.

Adakah perilaku Fooobjek (atau, kurang bagus, beberapa objek lain di lingkungan) yang dipengaruhi oleh foo()? Jika demikian, ujilah itu. Dan jika tidak, apa yang dilakukan metode ini?

Michael Brewer-Davis
sumber
4
Jadi apa yang akan Anda uji di sini? API publik Fooadalah public void foo(), di mana internal hanya terkait bar.
behelit
15
Hanya menguji API publik saja, sampai ada bug asli dengan efek samping yang perlu diuji. Sebagai contoh, memeriksa bahwa metode pribadi menutup koneksi HTTP-nya dengan benar adalah berlebihan sampai Anda menemukan bahwa metode pribadi tidak menutup koneksinya dengan benar, dan dengan demikian menyebabkan masalah besar. Pada titik itu, Mockito dan verify()menjadi sangat membantu memang, bahkan jika Anda tidak lagi beribadah di altar suci pengujian integrasi.
Dawngerpony
@ DuffJ Saya tidak menggunakan Java, tapi itu terdengar seperti sesuatu yang harus dideteksi oleh kompiler atau alat analisis kode Anda.
user247702
3
Saya setuju dengan DuffJ, sementara pemrograman fungsional itu menyenangkan, ada titik di mana kode Anda berinteraksi dengan dunia luar. Tidak masalah jika Anda menyebutnya "internal", "efek samping", atau "fungsionalitas", Anda pasti ingin menguji interaksi itu: jika itu terjadi, dan jika itu terjadi beberapa kali dan dengan argumen yang benar. @Stijn: ini mungkin contoh yang buruk (tetapi jika beberapa koneksi harus dibuka, dan hanya beberapa dari mereka yang ditutup, maka itu menjadi menarik). Contoh yang lebih baik adalah memeriksa cuaca data yang benar akan dikirim melalui koneksi.
Andras Balázs Lajtha
13

Jika Anda tidak ingin menggunakan DI atau Pabrik. Anda dapat memperbaiki kelas Anda dengan cara yang agak rumit:

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

Dan kelas ujian Anda:

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

Maka kelas yang memanggil metode foo Anda akan melakukannya seperti ini:

public class thirdClass {

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

Seperti yang Anda lihat ketika memanggil metode dengan cara ini, Anda tidak perlu mengimpor kelas Bar di kelas lain yang memanggil metode foo Anda yang mungkin merupakan sesuatu yang Anda inginkan.

Tentu saja downside adalah bahwa Anda mengizinkan penelepon untuk mengatur Objek Bar.

Semoga ini bisa membantu.

raspacorp
sumber
3
Saya pikir ini anti-pola. Ketergantungan harus disuntikkan, titik. Mengizinkan ketergantungan yang disuntikkan secara opsional hanya untuk tujuan pengujian secara sengaja menghindari peningkatan kode dan secara sengaja menguji sesuatu yang berbeda dari kode yang berjalan dalam produksi. Keduanya adalah hal-hal yang mengerikan dan mengerikan untuk dilakukan.
ErikE
8

Solusi untuk kode contoh Anda menggunakan PowerMockito.whenNew

  • mockito-semua 1.10.8
  • powermock-core 1.6.1
  • powermock-module-junit4 1.6.1
  • powermock-api-mockito 1.6.1
  • Junit 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();

    }

}

Output JUnit Output JUnit

javaPlease42
sumber
8

Saya pikir Mockito @InjectMocksadalah cara untuk pergi.

Bergantung pada niat Anda, Anda dapat menggunakan:

  1. Injeksi konstruktor
  2. Injeksi setter properti
  3. Injeksi lapangan

Info lebih lanjut dalam dokumen

Di bawah ini adalah contoh dengan injeksi lapangan:

Kelas:

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

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

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

Uji:

@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();
    }
}
siulkilulki
sumber
3

Ya, jika Anda benar-benar ingin / perlu melakukannya, Anda dapat menggunakan PowerMock. Ini harus dianggap sebagai upaya terakhir. Dengan PowerMock, Anda dapat membuatnya mengejek dari panggilan ke konstruktor. Kemudian lakukan verifikasi pada mock. Yang mengatakan, jawaban csturtz adalah "benar".

Berikut adalah tautan ke Mock konstruksi objek baru

John B
sumber
0

Cara sederhana lainnya adalah menambahkan beberapa pernyataan log ke bar.someMethod () dan kemudian memastikan Anda dapat melihat pesan tersebut ketika tes Anda dieksekusi, lihat contoh di sini: Cara melakukan JUnit menegaskan pesan di logger

Itu sangat berguna ketika Bar.someMethod () Anda private.

Nestor Milyaev
sumber