Menetapkan / ref parameter dalam Moq

293

Apakah mungkin untuk menetapkan parameter out/ refmenggunakan Moq (3.0+)?

Saya telah melihat menggunakan Callback(), tetapi Action<>tidak mendukung parameter ref karena didasarkan pada obat generik. Saya juga lebih suka meletakkan constraint ( It.Is) pada input refparameter, meskipun saya bisa melakukannya di callback.

Saya tahu bahwa Badak Mocks mendukung fungsi ini, tetapi proyek yang saya kerjakan sudah menggunakan Moq.

Richard Szalay
sumber
4
Tanya Jawab ini tentang Moq 3. Moq 4.8 memiliki banyak dukungan yang ditingkatkan untuk parameter by-ref, mulai dari It.IsAny<T>()pencocokan seperti ( ref It.Ref<T>.IsAny) hingga dukungan untuk pengaturan .Callback()dan .Returns()melalui tipe delegasi khusus yang cocok dengan tanda tangan metode. Metode yang dilindungi sama-sama didukung. Lihat misalnya jawaban saya di bawah ini .
stakx - tidak lagi berkontribusi
Anda dapat menggunakan It.Ref <TValue> .Isany untuk setiap metode yang menggunakan parameter out. Sebagai contoh: moq.Setup (x => x.Method (out It.Ref <string> .IsAny) .Returns (Nilai TV);
MikBTC

Jawaban:

117

Moq versi 4.8 (atau lebih baru) memiliki banyak dukungan yang ditingkatkan untuk parameter by-ref:

public interface IGobbler
{
    bool Gobble(ref int amount);
}

delegate void GobbleCallback(ref int amount);     // needed for Callback
delegate bool GobbleReturns(ref int amount);      // needed for Returns

var mock = new Mock<IGobbler>();
mock.Setup(m => m.Gobble(ref It.Ref<int>.IsAny))  // match any value passed by-ref
    .Callback(new GobbleCallback((ref int amount) =>
     {
         if (amount > 0)
         {
             Console.WriteLine("Gobbling...");
             amount -= 1;
         }
     }))
    .Returns(new GobbleReturns((ref int amount) => amount > 0));

int a = 5;
bool gobbleSomeMore = true;
while (gobbleSomeMore)
{
    gobbleSomeMore = mock.Object.Gobble(ref a);
}

Pola yang sama berfungsi untuk outparameter.

It.Ref<T>.IsAnyjuga berfungsi untuk inparameter C # 7 (karena mereka juga by-ref).

stakx - tidak lagi berkontribusi
sumber
2
ini solusinya, ini membiarkan Anda memiliki input sebagai ref, persis seperti itu akan berfungsi untuk input non ref. Ini memang peningkatan dukungan yang sangat bagus
grathad
5
Namun, solusi yang sama ini tidak berhasil out, bukan?
ATD
1
@ATD sebagian ya. Deklarasikan delegasi dengan parameter keluar dan tetapkan nilai dalam callback dengan sintaksis dari atas
royalTS
Layak disebutkan bahwa jika fungsi yang Anda ejek memiliki lebih banyak argumen, maka tanda tangan panggilan balik harus mengikuti pola yang sama (bukan hanya parameter ref / out)
Yoav Feuerstein
320

Untuk 'keluar', berikut ini sepertinya berhasil untuk saya.

public interface IService
{
    void DoSomething(out string a);
}

[TestMethod]
public void Test()
{
    var service = new Mock<IService>();
    var expectedValue = "value";
    service.Setup(s => s.DoSomething(out expectedValue));

    string actualValue;
    service.Object.DoSomething(out actualValue);
    Assert.AreEqual(expectedValue, actualValue);
}

Saya menduga bahwa Moq melihat nilai 'expectedValue' ketika Anda memanggil Setup dan mengingatnya.

Sebab ref, saya juga mencari jawaban.

Saya menemukan panduan QuickStart berikut berguna: https://github.com/Moq/moq4/wiki/Quickstart

Craig Celeste
sumber
7
Saya pikir masalah yang saya miliki adalah di mana tidak ada metode untuk menetapkan par / out ref dari metodeSetup
Richard Szalay
1
Saya tidak punya solusi untuk menetapkan parameter ref. Contoh ini tidak menetapkan nilai "nilai output" ke 'b'. Moq tidak mengeksekusi Ekspresi yang Anda berikan ke Setup, ia menganalisisnya dan menyadari bahwa Anda memberikan 'a' untuk nilai output, jadi itu terlihat pada nilai sekarang dari 'a' dan mengingatnya untuk panggilan berikutnya.
Craig Celeste
Lihat juga contoh keluar dan ref di: code.google.com/p/moq/wiki/QuickStart
TrueWill
9
Ini tidak akan berfungsi untuk saya ketika metode antarmuka Mocked dieksekusi dalam lingkup yang berbeda yang memiliki variabel output referensi sendiri (misalnya di dalam metode kelas lain.) Contoh yang diberikan di atas nyaman karena eksekusi terjadi dalam lingkup yang sama dengan pengaturan tiruan, namun terlalu sederhana untuk menyelesaikan semua skenario. Dukungan untuk penanganan eksplisit dari nilai out / ref lemah dalam moq (seperti yang dikatakan oleh orang lain, ditangani pada waktu eksekusi).
John K
2
+1: ini adalah jawaban yang bermanfaat. Tetapi: jika tipe parameter out adalah kelas daripada tipe build-in seperti string - Saya tidak percaya ini akan berhasil. Sudah mencobanya hari ini. Objek tiruan mensimulasikan panggilan dan mengembalikan null melalui parameter "keluar".
azheglov
86

EDIT : Dalam Moq 4.10, Anda sekarang dapat melewati delegasi yang memiliki parameter keluar atau ref langsung ke fungsi Callback:

mock
  .Setup(x=>x.Method(out d))
  .Callback(myDelegate)
  .Returns(...); 

Anda harus mendefinisikan delegasi dan membuat instantiate:

...
.Callback(new MyDelegate((out decimal v)=>v=12m))
...

Untuk versi Moq sebelum 4.10:

Avner Kashtan menyediakan metode ekstensi di blog-nya yang memungkinkan pengaturan parameter keluar dari callback: Parameter Moq, Callbacks dan Out: kasus tepi yang sangat rumit

Solusinya elegan dan hacky. Elegan karena memberikan sintaks yang lancar yang terasa di rumah dengan panggilan balik Moq lainnya. Dan hacky karena mengandalkan memanggil beberapa API Moq internal melalui refleksi.

Metode ekstensi yang disediakan di tautan di atas tidak dapat dikompilasi untuk saya, jadi saya telah menyediakan versi yang diedit di bawah. Anda harus membuat tanda tangan untuk setiap jumlah parameter input yang Anda miliki; Saya telah memberikan 0 dan 1, tetapi memperluasnya harus sederhana:

public static class MoqExtensions
{
    public delegate void OutAction<TOut>(out TOut outVal);
    public delegate void OutAction<in T1,TOut>(T1 arg1, out TOut outVal);

    public static IReturnsThrows<TMock, TReturn> OutCallback<TMock, TReturn, TOut>(this ICallback<TMock, TReturn> mock, OutAction<TOut> action)
        where TMock : class
    {
        return OutCallbackInternal(mock, action);
    }

    public static IReturnsThrows<TMock, TReturn> OutCallback<TMock, TReturn, T1, TOut>(this ICallback<TMock, TReturn> mock, OutAction<T1, TOut> action)
        where TMock : class
    {
        return OutCallbackInternal(mock, action);
    }

    private static IReturnsThrows<TMock, TReturn> OutCallbackInternal<TMock, TReturn>(ICallback<TMock, TReturn> mock, object action)
        where TMock : class
    {
        mock.GetType()
            .Assembly.GetType("Moq.MethodCall")
            .InvokeMember("SetCallbackWithArguments", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, mock,
                new[] { action });
        return mock as IReturnsThrows<TMock, TReturn>;
    }
}

Dengan metode ekstensi di atas, Anda dapat menguji antarmuka tanpa parameter seperti:

public interface IParser
{
    bool TryParse(string token, out int value);
}

.. dengan pengaturan Moq berikut:

    [TestMethod]
    public void ParserTest()
    {
        Mock<IParser> parserMock = new Mock<IParser>();

        int outVal;
        parserMock
            .Setup(p => p.TryParse("6", out outVal))
            .OutCallback((string t, out int v) => v = 6)
            .Returns(true);

        int actualValue;
        bool ret = parserMock.Object.TryParse("6", out actualValue);

        Assert.IsTrue(ret);
        Assert.AreEqual(6, actualValue);
    }



Sunting : Untuk mendukung metode pengembalian kosong, Anda hanya perlu menambahkan metode kelebihan muatan baru:

public static ICallbackResult OutCallback<TOut>(this ICallback mock, OutAction<TOut> action)
{
    return OutCallbackInternal(mock, action);
}

public static ICallbackResult OutCallback<T1, TOut>(this ICallback mock, OutAction<T1, TOut> action)
{
    return OutCallbackInternal(mock, action);
}

private static ICallbackResult OutCallbackInternal(ICallback mock, object action)
{
    mock.GetType().Assembly.GetType("Moq.MethodCall")
        .InvokeMember("SetCallbackWithArguments", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, mock, new[] { action });
    return (ICallbackResult)mock;
}

Ini memungkinkan pengujian antarmuka seperti:

public interface IValidationRule
{
    void Validate(string input, out string message);
}

[TestMethod]
public void ValidatorTest()
{
    Mock<IValidationRule> validatorMock = new Mock<IValidationRule>();

    string outMessage;
    validatorMock
        .Setup(v => v.Validate("input", out outMessage))
        .OutCallback((string i, out string m) => m  = "success");

    string actualMessage;
    validatorMock.Object.Validate("input", out actualMessage);

    Assert.AreEqual("success", actualMessage);
}
Scott Wegner
sumber
5
@ Willbert, saya telah memperbarui jawaban saya dengan kelebihan tambahan untuk fungsi void-return.
Scott Wegner
2
Saya telah menggunakan solusi ini di test suite kami dan telah berfungsi. Namun sejak memperbarui ke Moq 4.10, tidak lagi.
Ristogod
2
Sepertinya rusak di komit ini github.com/moq/moq4/commit/… . Mungkin ada cara yang lebih baik untuk melakukan ini sekarang?
sparkplug
1
fyi di Moq4, MethodCall sekarang menjadi properti pengaturan, jadi nyali dari OutCallbackInternal di atas berubah menjadivar methodCall = mock.GetType().GetProperty("Setup").GetValue(mock); mock.GetType().Assembly.GetType("Moq.MethodCall") .InvokeMember("SetCallbackResponse", BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance, null, methodCall, new[] { action });
mike mckechnie
1
@Ristogod, dengan pembaruan ke Moq 4.10, Anda sekarang dapat mengirimkan delegasi yang memiliki parameter keluar atau ref langsung ke fungsi Callback: mock.Setup(x=>x.Method(out d)).Callback(myDelegate).Returns(...);Anda harus menetapkan delegasi dan membuat instantiate:...Callback(new MyDelegate((out decimal v)=>v=12m))...;
esteuart
48

Ini adalah dokumentasi dari situs Moq :

// out arguments
var outString = "ack";
// TryParse will return true, and the out argument will return "ack", lazy evaluated
mock.Setup(foo => foo.TryParse("ping", out outString)).Returns(true);


// ref arguments
var instance = new Bar();
// Only matches if the ref argument to the invocation is the same instance
mock.Setup(foo => foo.Submit(ref instance)).Returns(true);
Kosau
sumber
5
Ini pada dasarnya sama dengan jawaban Parched dan memiliki batasan yang sama, dalam hal itu tidak dapat mengubah nilai keluar tergantung pada input juga tidak dapat menanggapi parameter ref.
Richard Szalay
@ Richard Szalay, Anda mungkin, tetapi Anda harus memiliki pengaturan terpisah dengan parameter "outString" yang terpisah
Sielu
3

Untuk mengembalikan nilai bersama dengan pengaturan parameter ref, berikut adalah sepotong kode:

public static class MoqExtensions
{
    public static IReturnsResult<TMock> DelegateReturns<TMock, TReturn, T>(this IReturnsThrows<TMock, TReturn> mock, T func) where T : class
        where TMock : class
    {
        mock.GetType().Assembly.GetType("Moq.MethodCallReturn`2").MakeGenericType(typeof(TMock), typeof(TReturn))
            .InvokeMember("SetReturnDelegate", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, mock,
                new[] { func });
        return (IReturnsResult<TMock>)mock;
    }
}

Kemudian nyatakan delegasi Anda sendiri yang cocok dengan tanda tangan metode yang akan diejek dan sediakan implementasi metode Anda sendiri.

public delegate int MyMethodDelegate(int x, ref int y);

    [TestMethod]
    public void TestSomething()
    {
        //Arrange
        var mock = new Mock<ISomeInterface>();
        var y = 0;
        mock.Setup(m => m.MyMethod(It.IsAny<int>(), ref y))
        .DelegateReturns((MyMethodDelegate)((int x, ref int y)=>
         {
            y = 1;
            return 2;
         }));
    }
Victor Mukherjee
sumber
Apakah ini berfungsi ketika Anda tidak memiliki akses ke variabel yang akan diteruskan sebagai y? Saya memiliki fungsi yang membawa dua argumen referensi ke DayOfWeek. Saya perlu mengatur keduanya pada hari tertentu di mock stub dan argumen ketiga adalah konteks basis data mock. Tetapi metode delegasi tidak dipanggil. Sepertinya Moq akan mengharapkan untuk mencocokkan y lokal yang dilewatkan atau fungsi "MyMethod" Anda. Apakah itu cara kerjanya untuk contoh Anda? Terima kasih.
Greg Veres
3

Membangun pada awnser Billy Jakes, saya membuat metode tiruan yang sepenuhnya dinamis dengan parameter keluar. Saya memposting ini di sini untuk siapa saja yang merasa berguna (mungkin hanya masa depan saya).

// Define a delegate with the params of the method that returns void.
delegate void methodDelegate(int x, out string output);

// Define a variable to store the return value.
bool returnValue;

// Mock the method: 
// Do all logic in .Callback and store the return value.
// Then return the return value in the .Returns
mockHighlighter.Setup(h => h.SomeMethod(It.IsAny<int>(), out It.Ref<int>.IsAny))
  .Callback(new methodDelegate((int x, out int output) =>
  {
    // do some logic to set the output and return value.
    output = ...
    returnValue = ...
  }))
  .Returns(() => returnValue);
Martijn
sumber
2

Saya yakin solusi Scott bekerja pada satu titik,

Tapi itu argumen yang bagus untuk tidak menggunakan refleksi untuk mengintip apis pribadi. Sudah rusak sekarang.

Saya dapat menetapkan parameter menggunakan delegasi

      delegate void MockOutDelegate(string s, out int value);

    public void SomeMethod()
    {
        ....

         int value;
         myMock.Setup(x => x.TryDoSomething(It.IsAny<string>(), out value))
            .Callback(new MockOutDelegate((string s, out int output) => output = userId))
            .Returns(true);
    }
Billy Jake O'Connor
sumber
1

Ini bisa menjadi solusi.

[Test]
public void TestForOutParameterInMoq()
{
  //Arrange
  _mockParameterManager= new Mock<IParameterManager>();

  Mock<IParameter > mockParameter= new Mock<IParameter >();
  //Parameter affectation should be useless but is not. It's really used by Moq 
  IParameter parameter= mockParameter.Object;

  //Mock method used in UpperParameterManager
  _mockParameterManager.Setup(x => x.OutMethod(out parameter));

  //Act with the real instance
  _UpperParameterManager.UpperOutMethod(out parameter);

  //Assert that method used on the out parameter of inner out method are really called
  mockParameter.Verify(x => x.FunctionCalledInOutMethodAfterInnerOutMethod(),Times.Once());

}
Fabrice
sumber
1
Ini pada dasarnya sama dengan jawaban Parched dan memiliki batasan yang sama, dalam hal itu tidak dapat mengubah nilai keluar tergantung pada input juga tidak dapat menanggapi parameter ref.
Richard Szalay
1

Saya bergumul dengan banyak saran di sini sebelum saya dengan sederhana membuat instance dari kelas 'Palsu' baru yang mengimplementasikan antarmuka apa pun yang Anda coba tiru. Kemudian Anda cukup mengatur nilai parameter keluar dengan metode itu sendiri.

Casey O'Brien
sumber
0

Saya berjuang dengan ini selama satu jam sore ini dan tidak dapat menemukan jawaban di mana pun. Setelah bermain-main sendiri dengan itu saya bisa menemukan solusi yang bekerja untuk saya.

string firstOutParam = "first out parameter string";
string secondOutParam = 100;
mock.SetupAllProperties();
mock.Setup(m=>m.Method(out firstOutParam, out secondOutParam)).Returns(value);

Kuncinya di sini adalah mock.SetupAllProperties();yang akan mematikan semua properti untuk Anda. Ini mungkin tidak bekerja dalam setiap tes skenario, tetapi jika semua yang Anda pedulikan adalah mendapatkan return valuedari YourMethodmaka ini baik-baik saja kemauan kerja.

Maxshuty
sumber