Mengolok-olok konstruktor dengan parameter

90

Saya memiliki kelas seperti di bawah ini:

public class A {
    public A(String test) {
        bla bla bla
    }

    public String check() {
        bla bla bla
    }
}

Logika dalam konstruktor A(String test)dan check()hal-hal yang saya coba tiru. Saya ingin panggilan seperti: new A($$$any string$$$).check()mengembalikan string tiruan "test".

Saya mencoba:

 A a = mock(A.class); 
 when(a.check()).thenReturn("test");

 String test = a.check(); // to this point, everything works. test shows as "tests"

 whenNew(A.class).withArguments(Matchers.anyString()).thenReturn(rk);
 // also tried:
 //whenNew(A.class).withParameterTypes(String.class).withArguments(Matchers.anyString()).thenReturn(rk);

 new A("random string").check();  // this doesn't work

Tapi sepertinya itu tidak berhasil. new A($$$any string$$$).check()masih melalui logika konstruktor alih-alih mengambil objek tiruan dari A.

Shengjie
sumber
apakah metode mocked check () Anda berfungsi dengan benar?
Ben Glasser
@BenGlasser check () berfungsi dengan baik. Just the whenNew sepertinya tidak berfungsi sama sekali. Saya juga memperbarui deskripsi.
Shengjie

Jawaban:

93

Kode yang Anda poskan berfungsi untuk saya dengan versi terbaru Mockito dan Powermockito. Mungkin Anda belum menyiapkan A? Coba ini:

A. java

public class A {
     private final String test;

    public A(String test) {
        this.test = test;
    }

    public String check() {
        return "checked " + this.test;
    }
}

MockA.java

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

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

@RunWith(PowerMockRunner.class)
@PrepareForTest(A.class)
public class MockA {
    @Test
    public void test_not_mocked() throws Throwable {
        assertThat(new A("random string").check(), equalTo("checked random string"));
    }
    @Test
    public void test_mocked() throws Throwable {
         A a = mock(A.class); 
         when(a.check()).thenReturn("test");
         PowerMockito.whenNew(A.class).withArguments(Mockito.anyString()).thenReturn(a);
         assertThat(new A("random string").check(), equalTo("test"));
    }
}

Kedua pengujian harus lulus dengan mockito 1.9.0, powermockito 1.4.12 dan junit 4.8.2

Alban
sumber
25
Perhatikan juga bahwa jika konstruktor dipanggil dari kelas lain, sertakan dalam daftar diPrepareForTest
Jeff E
Ada yang tahu kenapa kita harus mempersiapkan diri ketika "PowerMockito.whenNew" dipanggil?
udayanga
50

Sepengetahuan saya, Anda tidak bisa mengejek konstruktor dengan mockito, hanya metode. Tetapi menurut wiki di halaman kode google Mockito ada cara untuk meniru perilaku konstruktor dengan membuat metode di kelas Anda yang mengembalikan contoh baru dari kelas itu. maka Anda bisa mengejek metode itu. Di bawah ini adalah kutipan langsung dari wiki Mockito :

Pola 1 - menggunakan metode satu baris untuk pembuatan objek

Untuk menggunakan pola 1 (menguji kelas yang disebut MyClass), Anda akan mengganti panggilan seperti

   Foo foo = new Foo( a, b, c );

dengan

   Foo foo = makeFoo( a, b, c );

dan tulis metode satu baris

   Foo makeFoo( A a, B b, C c ) { 
        return new Foo( a, b, c );
   }

Penting bahwa Anda tidak memasukkan logika apa pun ke dalam metode; hanya satu baris yang membuat objek. Alasannya adalah bahwa metode itu sendiri tidak akan pernah diuji unit.

Saat Anda datang untuk menguji kelas, objek yang Anda uji sebenarnya adalah mata-mata Mockito, dengan metode ini diganti, untuk mengembalikan tiruan. Oleh karena itu, apa yang Anda uji bukanlah kelas itu sendiri, tetapi versi modifikasi yang sangat sedikit.

Kelas pengujian Anda mungkin berisi anggota seperti

  @Mock private Foo mockFoo;
  private MyClass toTest = spy(new MyClass());

Terakhir, di dalam metode pengujian Anda, Anda mengejek panggilan ke makeFoo dengan garis seperti

  doReturn( mockFoo )
      .when( toTest )
      .makeFoo( any( A.class ), any( B.class ), any( C.class ));

Anda bisa menggunakan matcher yang lebih spesifik daripada any () jika Anda ingin memeriksa argumen yang diteruskan ke konstruktor.

Jika Anda hanya ingin mengembalikan objek yang diejek dari kelas Anda, saya pikir ini seharusnya berhasil untuk Anda. Bagaimanapun, Anda dapat membaca lebih lanjut tentang pembuatan objek tiruan di sini:

http://code.google.com/p/mockito/wiki/MockingObjectCreation

Ben Glasser
sumber
21
+1, Saya tidak suka kenyataan bahwa saya perlu menyesuaikan kode sumber saya agar lebih ramah mockito. Terima kasih telah berbagi.
Shengjie
23
Tidak pernah buruk untuk memiliki kode sumber yang lebih dapat diuji, atau untuk menghindari anti-pola testability saat Anda menulis kode Anda. Jika Anda menulis sumber yang lebih dapat diuji, secara otomatis lebih dapat dipelihara. Mengisolasi panggilan konstruktor Anda dalam metodenya sendiri hanyalah salah satu cara untuk mencapai ini.
Dawood ibn Kareem
1
Menulis kode yang dapat diuji itu bagus. Dipaksa mendesain ulang kelas A agar saya bisa menulis tes untuk kelas B yang bergantung pada A, karena A memiliki ketergantungan hardcode pada C, terasa ... kurang bagus. Ya, kodenya akan lebih baik pada akhirnya, tetapi berapa banyak kelas yang akan saya desain ulang sehingga saya dapat menyelesaikan menulis satu tes?
Mark Wood
@MarkWood dalam pengalaman saya, pengalaman pengujian yang kikuk umumnya merupakan tanda dari beberapa cacat desain. IRL jika Anda menguji konstruktor, kode Anda mungkin meneriaki Anda untuk pabrik atau injeksi ketergantungan. Jika Anda mengikuti pola desain tipikal untuk kedua kasus tersebut, kode Anda menjadi lebih mudah untuk diuji dan digunakan secara umum. Jika Anda menguji konstruktor karena memiliki banyak logika di sana, Anda mungkin memerlukan beberapa lapisan polimorfisme, atau Anda dapat memindahkan logika tersebut ke metode inisialisasi.
Ben Glasser
12

Tanpa Menggunakan Powermock .... Lihat contoh di bawah ini berdasarkan jawaban Ben Glasser karena saya butuh waktu untuk mengetahuinya .. harap itu menghemat waktu ...

Kelas Asli:

public class AClazz {

    public void updateObject(CClazz cClazzObj) {
        log.debug("Bundler set.");
        cClazzObj.setBundler(new BClazz(cClazzObj, 10));
    } 
}

Kelas yang Dimodifikasi:

@Slf4j
public class AClazz {

    public void updateObject(CClazz cClazzObj) {
        log.debug("Bundler set.");
        cClazzObj.setBundler(getBObject(cClazzObj, 10));
    }

    protected BClazz getBObject(CClazz cClazzObj, int i) {
        return new BClazz(cClazzObj, 10);
    }
 }

Kelas Tes

public class AClazzTest {

    @InjectMocks
    @Spy
    private AClazz aClazzObj;

    @Mock
    private CClazz cClazzObj;

    @Mock
    private BClazz bClassObj;

    @Before
    public void setUp() throws Exception {
        Mockito.doReturn(bClassObj)
               .when(aClazzObj)
               .getBObject(Mockito.eq(cClazzObj), Mockito.anyInt());
    }

    @Test
    public void testConfigStrategy() {
        aClazzObj.updateObject(cClazzObj);

        Mockito.verify(cClazzObj, Mockito.times(1)).setBundler(bClassObj);
    }
}
pengguna666
sumber
7

Dengan mockito Anda bisa menggunakan withSettings (), misalnya jika CounterService membutuhkan 2 dependensi, Anda bisa meneruskannya sebagai tiruan:

UserService userService = Mockito.mock(UserService.class); SearchService searchService = Mockito.mock(SearchService.class); CounterService counterService = Mockito.mock(CounterService.class, withSettings().useConstructor(userService, searchService));

MevlütÖzdemir
sumber
Menurut saya, jawaban termudah dan terbaik. Terima kasih.
4

Mockito memiliki keterbatasan dalam menguji metode final, statis, dan privat.

dengan pustaka pengujian jMockit, Anda dapat melakukan beberapa hal dengan sangat mudah dan langsung seperti di bawah ini:

Konstruktor tiruan dari kelas java.io.File:

new MockUp<File>(){
    @Mock
    public void $init(String pathname){
        System.out.println(pathname);
        // or do whatever you want
    }
};
  • nama konstruktor publik harus diganti dengan $ init
  • argumen dan pengecualian yang diberikan tetap sama
  • Jenis pengembalian harus didefinisikan sebagai kosong

Meniru metode statis:

  • Hapus statis dari metode tanda tangan tiruan
  • tanda tangan metode tetap sama jika tidak
Amit Kaneria
sumber