Membuat metode yang dipermainkan mengembalikan argumen yang diteruskan ke sana

674

Pertimbangkan tanda tangan metode seperti:

public String myFunction(String abc);

Bisakah Mockito membantu mengembalikan string yang sama dengan metode yang diterima?

Abhijeet Kashnia
sumber
Ok, bagaimana dengan kerangka java mocking pada umumnya ... Apakah ini mungkin dengan kerangka kerja lain, atau haruskah saya membuat rintisan bodoh untuk meniru perilaku yang saya inginkan?
Abhijeet Kashnia

Jawaban:

1001

Anda dapat membuat Jawaban di Mockito. Mari kita asumsikan, kita memiliki antarmuka bernama Aplikasi dengan metode myFunction.

public interface Application {
  public String myFunction(String abc);
}

Inilah metode pengujian dengan jawaban Mockito:

public void testMyFunction() throws Exception {
  Application mock = mock(Application.class);
  when(mock.myFunction(anyString())).thenAnswer(new Answer<String>() {
    @Override
    public String answer(InvocationOnMock invocation) throws Throwable {
      Object[] args = invocation.getArguments();
      return (String) args[0];
    }
  });

  assertEquals("someString",mock.myFunction("someString"));
  assertEquals("anotherString",mock.myFunction("anotherString"));
}

Karena Mockito 1.9.5 dan Java 8, Anda juga dapat menggunakan ekspresi lambda:

when(myMock.myFunction(anyString())).thenAnswer(i -> i.getArguments()[0]);
Steve
sumber
1
Ini juga yang saya cari. Terima kasih! Tapi masalah saya berbeda. Saya ingin mengejek layanan ketekunan (EJB) yang menyimpan objek dan mengembalikannya dengan nama.
migu
7
Saya membuat kelas tambahan yang membungkus penciptaan jawaban. Jadi kodenya berbunyi sepertiwhen(...).then(Return.firstParameter())
SpaceTrucker
69
Dengan Java 8 lambdas, sangat mudah untuk mengembalikan argumen pertama, bahkan untuk kelas tertentu, yaitu when(foo(any()).then(i -> i.getArgumentAt(0, Bar.class)). Dan Anda bisa menggunakan referensi metode dan memanggil metode nyata.
Paweł Dyda
Ini memecahkan masalah saya dengan metode pengembalian Iterator<? extends ClassName>yang menyebabkan semua jenis masalah dalam thenReturn()pernyataan.
Michael Shopsin
16
Dengan Java 8 dan Mockito <1.9.5 maka jawaban Paweł menjadiwhen(foo(any()).thenAnswer(i -> i.getArguments()[0])
Graeme Moss
567

Jika Anda memiliki Mockito 1.9.5 atau lebih tinggi, ada metode statis baru yang dapat membuat Answerobjek untuk Anda. Anda perlu menulis sesuatu seperti

import static org.mockito.Mockito.when;
import static org.mockito.AdditionalAnswers.returnsFirstArg;

when(myMock.myFunction(anyString())).then(returnsFirstArg());

atau sebagai alternatif

doAnswer(returnsFirstArg()).when(myMock).myFunction(anyString());

Perhatikan bahwa returnsFirstArg()metode ini statis di AdditionalAnswerskelas, yang baru untuk Mockito 1.9.5; jadi Anda akan memerlukan impor statis yang tepat.

Dawood ibn Kareem
sumber
17
Catatan: itu when(...).then(returnsFirstArg()), saya keliru when(...).thenReturn(returnsFirstArg())yang memberijava.lang.ClassCastException: org.mockito.internal.stubbing.answers.ReturnsArgumentAt cannot be cast to
Benedikt Köppel
1
Catatan: returnsFirstArg () mengembalikan Jawaban <> daripada nilai argumen. Mendapat 'Foo (java.lang.String) tidak dapat diterapkan ke' (org.mockito.stubbing.Answer <java.lang.Object>) '' ketika mencoba menelepon .thenReturn (Foo baru (returnsFirstArg ()))
Lu55
Saya selalu perlu google jawaban ini lagi dan lagi dan lagi selama beberapa tahun terakhir, karena saya tidak dapat mengingat "AdditionalAnswers" dan saya hanya membutuhkannya sangat jarang. Lalu saya bertanya-tanya bagaimana sih saya bisa membangun skenario itu karena saya tidak dapat menemukan dependensi yang diperlukan. Tidak bisakah ini ditambahkan langsung ke mockito? : /
BAERUS
2
Jawaban Steve lebih umum. Yang ini hanya memungkinkan Anda untuk mengembalikan argumen mentah. Jika Anda ingin memproses argumen itu dan mengembalikan hasilnya, maka Steve menjawab aturan. Saya membatalkan keduanya karena keduanya berguna.
akostadinov
FYI, kita harus mengimpor static org.mockito.AdditionalAnswers.returnsFirstArg. ini untuk menggunakan returnsFirstArg. Juga, saya bisa melakukannya when(myMock.myFunction(any())).then(returnsFirstArg())di Mockito 2.20. *
gtiwari333
78

Dengan Java 8 dimungkinkan untuk membuat jawaban satu baris bahkan dengan versi Mockito yang lebih lama:

when(myMock.myFunction(anyString()).then(i -> i.getArgumentAt(0, String.class));

Tentu saja ini tidak berguna seperti yang AdditionalAnswersdisarankan oleh David Wallace, tetapi mungkin berguna jika Anda ingin mengubah argumen "dengan cepat".

Paweł Dyda
sumber
1
Cemerlang. Terima kasih. Jika argumennya adalah long, apakah ini masih bisa bekerja dengan tinju dan Long.class?
vikingsteve
1
.getArgumentAt (..) tidak ditemukan untuk saya tetapi .getArgument (1) berhasil (mockito 2.6.2)
Curtis Yallop
41

Saya punya masalah yang sangat mirip. Tujuannya adalah untuk mengejek layanan yang tetap menggunakan Object dan dapat mengembalikannya dengan nama mereka. Layanannya terlihat seperti ini:

public class RoomService {
    public Room findByName(String roomName) {...}
    public void persist(Room room) {...}
}

Mock layanan menggunakan peta untuk menyimpan instance Kamar.

RoomService roomService = mock(RoomService.class);
final Map<String, Room> roomMap = new HashMap<String, Room>();

// mock for method persist
doAnswer(new Answer<Void>() {
    @Override
    public Void answer(InvocationOnMock invocation) throws Throwable {
        Object[] arguments = invocation.getArguments();
        if (arguments != null && arguments.length > 0 && arguments[0] != null) {
            Room room = (Room) arguments[0];
            roomMap.put(room.getName(), room);
        }
        return null;
    }
}).when(roomService).persist(any(Room.class));

// mock for method findByName
when(roomService.findByName(anyString())).thenAnswer(new Answer<Room>() {
    @Override
    public Room answer(InvocationOnMock invocation) throws Throwable {
        Object[] arguments = invocation.getArguments();
        if (arguments != null && arguments.length > 0 && arguments[0] != null) {
            String key = (String) arguments[0];
            if (roomMap.containsKey(key)) {
                return roomMap.get(key);
            }
        }
        return null;
    }
});

Kami sekarang dapat menjalankan tes kami di mock ini. Sebagai contoh:

String name = "room";
Room room = new Room(name);
roomService.persist(room);
assertThat(roomService.findByName(name), equalTo(room));
assertNull(roomService.findByName("none"));
migu
sumber
34

Dengan Java 8, jawaban Steve bisa menjadi

public void testMyFunction() throws Exception {
    Application mock = mock(Application.class);
    when(mock.myFunction(anyString())).thenAnswer(
    invocation -> {
        Object[] args = invocation.getArguments();
        return args[0];
    });

    assertEquals("someString", mock.myFunction("someString"));
    assertEquals("anotherString", mock.myFunction("anotherString"));
}

SUNTING: Lebih singkat lagi:

public void testMyFunction() throws Exception {
    Application mock = mock(Application.class);
    when(mock.myFunction(anyString())).thenAnswer(
        invocation -> invocation.getArgument(0));

    assertEquals("someString", mock.myFunction("someString"));
    assertEquals("anotherString", mock.myFunction("anotherString"));
}
Yiwei
sumber
6

Ini pertanyaan yang cukup lama tapi saya pikir masih relevan. Juga jawaban yang diterima hanya berfungsi untuk String. Sementara itu ada Mockito 2.1 dan beberapa impor telah berubah, jadi saya ingin membagikan jawaban saya saat ini:

import static org.mockito.AdditionalAnswers.returnsFirstArg;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;

@Mock
private MyClass myClass;

// this will return anything you pass, but it's pretty unrealistic
when(myClass.myFunction(any())).then(returnsFirstArg());
// it is more "life-like" to accept only the right type
when(myClass.myFunction(any(ClassOfArgument.class))).then(returnsFirstArg());

The myClass.myFunction akan terlihat seperti:

public class MyClass {
    public ClassOfArgument myFunction(ClassOfArgument argument){
        return argument;
    }  
}
LazR
sumber
4

Saya menggunakan sesuatu yang serupa (pada dasarnya itu pendekatan yang sama). Kadang-kadang berguna untuk memiliki objek tiruan mengembalikan output yang telah ditentukan untuk input tertentu. Seperti ini:

private Hashtable<InputObject,  OutputObject> table = new Hashtable<InputObject, OutputObject>();
table.put(input1, ouput1);
table.put(input2, ouput2);

...

when(mockObject.method(any(InputObject.class))).thenAnswer(
       new Answer<OutputObject>()
       {
           @Override
           public OutputObject answer(final InvocationOnMock invocation) throws Throwable
           {
               InputObject input = (InputObject) invocation.getArguments()[0];
               if (table.containsKey(input))
               {
                   return table.get(input);
               }
               else
               {
                   return null; // alternatively, you could throw an exception
               }
           }
       }
       );
martin
sumber
4

Anda mungkin ingin menggunakan verifikasi () dalam kombinasi dengan ArgumentCaptor untuk memastikan eksekusi dalam pengujian dan ArgumentCaptor untuk mengevaluasi argumen:

ArgumentCaptor<String> argument = ArgumentCaptor.forClass(String.class);
verify(mock).myFunction(argument.capture());
assertEquals("the expected value here", argument.getValue());

Nilai argumen jelas dapat diakses melalui argument.getValue () untuk manipulasi lebih lanjut / memeriksa / apa pun.

fl0w
sumber
3

Ini agak lama, tetapi saya datang ke sini karena saya memiliki masalah yang sama. Saya menggunakan JUnit tetapi kali ini di aplikasi Kotlin dengan mockk. Saya memposting sampel di sini untuk referensi dan perbandingan dengan mitra Java:

@Test
fun demo() {
  // mock a sample function
  val aMock: (String) -> (String) = mockk()

  // make it return the same as the argument on every invocation
  every {
    aMock.invoke(any())
  } answers {
    firstArg()
  }

  // test it
  assertEquals("senko", aMock.invoke("senko"))
  assertEquals("senko1", aMock.invoke("senko1"))
  assertNotEquals("not a senko", aMock.invoke("senko"))
}
Lachezar Balev
sumber
2

Anda dapat mencapai ini dengan menggunakan ArgumentCaptor

Bayangkan Anda memiliki fungsi kacang seperti itu.

public interface Application {
  public String myFunction(String abc);
}

Kemudian di kelas ujian Anda:

//Use ArgumentCaptor to capture the value
ArgumentCaptor<String> param = ArgumentCaptor.forClass(String.class);


when(mock.myFunction(param.capture())).thenAnswer(new Answer<String>() {
    @Override
    public String answer(InvocationOnMock invocation) throws Throwable {
      return param.getValue();//return the captured value.
    }
  });

ATAU jika Anda penggemar lambda cukup lakukan:

//Use ArgumentCaptor to capture the value
ArgumentCaptor<String> param = ArgumentCaptor.forClass(String.class);


when(mock.myFunction(param.capture()))
    .thenAnswer((invocation) -> param.getValue());

Ringkasan: Gunakan argumencaptor, untuk menangkap parameter yang dilewati. Kemudian sebagai jawaban, kembalikan nilai yang ditangkap menggunakan getValue.

Cyril Cherian
sumber
Ini tidak berfungsi (lagi?). Mengenai dokumen: Metode ini harus digunakan di dalam verifikasi. Itu berarti Anda hanya dapat menangkap nilai saat menggunakan metode verifikasi
Muhammed Misir
1. Tidak yakin apa yang Anda maksudkan dengan ini This doesn´t work (anymore?).saya bekerja pada contoh saya. 2. Maaf, saya tidak jelas pada poin yang Anda coba buat. Jawabannya khusus untuk pertanyaan OP.
Cyril Cherian