Memverifikasi parameter tertentu dengan Moq

169
public void SubmitMessagesToQueue_OneMessage_SubmitSuccessfully()
{
    var messageServiceClientMock = new Mock<IMessageServiceClient>();
    var queueableMessage = CreateSingleQueueableMessage();
    var message = queueableMessage[0];
    var xml = QueueableMessageAsXml(queueableMessage);
    messageServiceClientMock.Setup(proxy => proxy.SubmitMessage(xml)).Verifiable();
    //messageServiceClientMock.Setup(proxy => proxy.SubmitMessage(It.IsAny<XmlElement>())).Verifiable();

    var serviceProxyFactoryStub = new Mock<IMessageServiceClientFactory>();
    serviceProxyFactoryStub.Setup(proxyFactory => proxyFactory.CreateProxy()).Returns(essageServiceClientMock.Object);
    var loggerStub = new Mock<ILogger>();

    var client = new MessageClient(serviceProxyFactoryStub.Object, loggerStub.Object);
    client.SubmitMessagesToQueue(new List<IMessageRequestDTO> {message});

    //messageServiceClientMock.Verify(proxy => proxy.SubmitMessage(xml), Times.Once());
    messageServiceClientMock.Verify();
}

Saya mulai menggunakan Moq dan sedikit berjuang. Saya mencoba memverifikasi bahwa messageServiceClient menerima parameter yang tepat, yang merupakan XmlElement, tetapi saya tidak dapat menemukan cara untuk membuatnya berfungsi. Ini hanya berfungsi ketika saya tidak memeriksa nilai tertentu.

Ada ide?

Sebagian jawaban: Saya telah menemukan cara untuk menguji bahwa xml yang dikirim ke proxy sudah benar, tetapi saya masih berpikir itu bukan cara yang tepat untuk melakukannya.

public void SubmitMessagesToQueue_OneMessage_SubmitSuccessfully()
{
    var messageServiceClientMock = new Mock<IMessageServiceClient>();
    messageServiceClientMock.Setup(proxy => proxy.SubmitMessage(It.IsAny<XmlElement>())).Verifiable();
    var serviceProxyFactoryStub = new Mock<IMessageServiceClientFactory>();
    serviceProxyFactoryStub.Setup(proxyFactory => proxyFactory.CreateProxy()).Returns(messageServiceClientMock.Object);
    var loggerStub = new Mock<ILogger>();

    var client = new MessageClient(serviceProxyFactoryStub.Object, loggerStub.Object);
    var message = CreateMessage();
    client.SubmitMessagesToQueue(new List<IMessageRequestDTO> {message});

    messageServiceClientMock.Verify(proxy => proxy.SubmitMessage(It.Is<XmlElement>(xmlElement => XMLDeserializer<QueueableMessage>.Deserialize(xmlElement).Messages.Contains(message))), Times.Once());
}

Ngomong-ngomong, bagaimana saya bisa mengekstrak ekspresi dari panggilan Verifikasi?

Luis Mirabal
sumber

Jawaban:

250

Jika logika verifikasi non-sepele, itu akan berantakan untuk menulis metode lambda besar (seperti contoh Anda tunjukkan). Anda dapat meletakkan semua pernyataan pengujian dalam metode terpisah, tetapi saya tidak suka melakukan ini karena mengganggu aliran membaca kode tes.

Pilihan lain adalah menggunakan callback pada panggilan Setup untuk menyimpan nilai yang diteruskan ke metode yang diolok-olok, dan kemudian menulis Assertmetode standar untuk memvalidasinya. Sebagai contoh:

// Arrange
MyObject saveObject;
mock.Setup(c => c.Method(It.IsAny<int>(), It.IsAny<MyObject>()))
        .Callback<int, MyObject>((i, obj) => saveObject = obj)
        .Returns("xyzzy");

// Act
// ...

// Assert
// Verify Method was called once only
mock.Verify(c => c.Method(It.IsAny<int>(), It.IsAny<MyObject>()), Times.Once());
// Assert about saveObject
Assert.That(saveObject.TheProperty, Is.EqualTo(2));
Tebb kaya
sumber
6
Satu manfaat besar untuk pendekatan ini adalah bahwa ia akan memberi Anda kegagalan pengujian khusus untuk bagaimana objek salah (saat Anda menguji masing-masing secara individual).
Rob Church
1
Saya pikir saya adalah satu-satunya yang melakukan ini, senang melihat itu pendekatan yang masuk akal!
Will Appleby
3
Saya pikir menggunakan It.Is <MyObject> (validator) sesuai Mayo lebih baik karena menghindari cara yang agak canggung untuk menyimpan nilai parameter sebagai bagian dari lambda
stevec
apakah thread ini aman, misalnya saat menjalankan tes secara paralel?
Anton Tolken
@AntonTolken Saya belum mencobanya, tetapi dalam contoh saya, objek yang diperbarui adalah variabel lokal (saveObject) jadi itu harus aman thread.
Rich Tebb
112

Saya telah memverifikasi panggilan dengan cara yang sama - saya percaya ini adalah cara yang tepat untuk melakukannya.

mockSomething.Verify(ms => ms.Method(
    It.IsAny<int>(), 
    It.Is<MyObject>(mo => mo.Id == 5 && mo.description == "test")
  ), Times.Once());

Jika ekspresi lambda Anda menjadi berat, Anda dapat membuat fungsi yang mengambil MyObjectinput dan output true/ false...

mockSomething.Verify(ms => ms.Method(
    It.IsAny<int>(), 
    It.Is<MyObject>(mo => MyObjectFunc(mo))
  ), Times.Once());

private bool MyObjectFunc(MyObject myObject)
{
  return myObject.Id == 5 && myObject.description == "test";
}

Perlu diketahui juga bug dengan Mock di mana pesan kesalahan menyatakan bahwa metode dipanggil beberapa kali ketika tidak dipanggil sama sekali. Mereka mungkin telah memperbaikinya sekarang - tetapi jika Anda melihat pesan itu, Anda dapat mempertimbangkan memverifikasi bahwa metode itu sebenarnya dipanggil.

EDIT: Berikut adalah contoh panggilan verifikasi beberapa kali untuk skenario di mana Anda ingin memverifikasi bahwa Anda memanggil fungsi untuk setiap objek dalam daftar (misalnya).

foreach (var item in myList)
  mockRepository.Verify(mr => mr.Update(
    It.Is<MyObject>(i => i.Id == item.Id && i.LastUpdated == item.LastUpdated),
    Times.Once());

Pendekatan yang sama untuk pengaturan ...

foreach (var item in myList) {
  var stuff = ... // some result specific to the item
  this.mockRepository
    .Setup(mr => mr.GetStuff(item.itemId))
    .Returns(stuff);
}

Jadi setiap kali GetStuff dipanggil untuk itemId itu, itu akan mengembalikan barang-barang khusus untuk item itu. Atau, Anda bisa menggunakan fungsi yang mengambil itemId sebagai input dan mengembalikan barang.

this.mockRepository
    .Setup(mr => mr.GetStuff(It.IsAny<int>()))
    .Returns((int id) => SomeFunctionThatReturnsStuff(id));

Satu metode lain yang saya lihat di sebuah blog beberapa waktu lalu (Phil Haack mungkin?) Memiliki pengaturan kembali dari beberapa jenis objek dequeue - setiap kali fungsi dipanggil akan menarik item dari antrian.

Mayo
sumber
1
Terima kasih, itu masuk akal bagi saya. Apa yang masih saya tidak mengerti adalah kapan harus menentukan detail dalam Pengaturan atau Verifikasi. Cukup membingungkan. Saat ini, saya hanya mengizinkan apa pun di Setup dan menentukan nilai dalam Verifikasi.
Luis Mirabal
Menurut Anda bagaimana saya bisa memeriksa pesan ketika ada beberapa panggilan? Klien menerima pesan dan dapat membuat beberapa Pesan yang dapat antrean, yang akan berakhir di beberapa panggilan dan di masing-masing panggilan itu, saya harus memeriksa pesan yang berbeda. Saya masih berjuang dengan unit testing secara umum, saya belum terlalu akrab dengannya.
Luis Mirabal
Saya tidak berpikir ada peluru perak ajaib dalam hal bagaimana Anda harus melakukan ini. Dibutuhkan latihan dan Anda mulai menjadi lebih baik. Bagi saya, saya hanya menentukan parameter ketika saya memiliki sesuatu untuk membandingkannya dan ketika saya belum menguji parameter itu dalam pengujian lain. Adapun beberapa panggilan ada beberapa pendekatan. Untuk mengatur dan memverifikasi fungsi yang disebut beberapa kali, saya biasanya memanggil pengaturan atau memverifikasi (Times.Once ()) untuk setiap panggilan yang saya harapkan - sering dengan for loop. Anda dapat menggunakan parameter khusus untuk mengisolasi setiap panggilan.
Mayo
Saya menambahkan beberapa contoh untuk beberapa panggilan - lihat jawaban di atas.
Mayo
1
"Juga, waspadai bug dengan Mock di mana pesan kesalahan menyatakan bahwa metode itu dipanggil beberapa kali ketika tidak dipanggil sama sekali. Mereka mungkin sudah memperbaikinya sekarang - tetapi jika Anda melihat pesan itu Anda mungkin mempertimbangkan memverifikasi bahwa metode itu sebenarnya disebut. " - Bug seperti ini sepenuhnya membatalkan IMHO perpustakaan yang mengejek. Betapa ironisnya mereka tidak memiliki kode pengujian yang tepat untuk itu :-)
Gianluca Ghettini
20

Cara yang lebih sederhana adalah dengan melakukan:

ObjectA.Verify(
    a => a.Execute(
        It.Is<Params>(p => p.Id == 7)
    )
);
dmitry.sergeyev
sumber
Sepertinya saya tidak bisa menjalankannya, saya mencoba memverifikasi bahwa metode saya dipanggil dengan Code.WRCC sebagai parameter. Tetapi pengujian saya selalu berlalu, meskipun parameter yang lulus adalah WRDD .. m.Setup(x => x.CreateSR(Code.WRDD)).ReturnsAsync(0); await ClassUnderTest.CompleteCC(accountKey, It.IsAny<int>(), mockRequest); m.Verify(x => x.CreateSR(It.Is<Code>(p => p == Code.WRCC)), Times.Once());
Greg Quinn
1

Saya percaya bahwa masalah dalam kenyataan bahwa Moq akan memeriksa kesetaraan. Dan, karena XmlElement tidak mengesampingkan Persamaan, implementasinya akan memeriksa kesetaraan referensi.

Tidak bisakah Anda menggunakan objek khusus, sehingga Anda dapat menimpa sama dengan?

Fernando
sumber
Ya, saya akhirnya melakukan itu. Saya menyadari bahwa masalahnya adalah memeriksa Xml. Di bagian kedua dari pertanyaan saya menambahkan jawaban yang mungkin deserialising xml ke objek
Luis Mirabal
1

Punya salah satu dari ini juga, tetapi parameter tindakan adalah antarmuka tanpa properti publik. Akhirnya menggunakan It.Is () dengan metode terpisah dan dalam metode ini harus melakukan beberapa mengejek antarmuka

public interface IQuery
{
    IQuery SetSomeFields(string info);
}

void DoSomeQuerying(Action<IQuery> queryThing);

mockedObject.Setup(m => m.DoSomeQuerying(It.Is<Action<IQuery>>(q => MyCheckingMethod(q)));

private bool MyCheckingMethod(Action<IQuery> queryAction)
{
    var mockQuery = new Mock<IQuery>();
    mockQuery.Setup(m => m.SetSomeFields(It.Is<string>(s => s.MeetsSomeCondition())
    queryAction.Invoke(mockQuery.Object);
    mockQuery.Verify(m => m.SetSomeFields(It.Is<string>(s => s.MeetsSomeCondition(), Times.Once)
    return true
}
ds4940
sumber