Adedi / ref parametrelerini atama


293

Bir out/ atamak mümkün mürefMoq (3.0+) kullanarak parametre ?

Kullanmaya baktım Callback(), ancak Action<>jeneriklere dayalı olduğu için ref parametrelerini desteklemiyorum. Ben de tercihen geri çağrısında yapabilirsiniz rağmen It.Is, refparametre girişine bir kısıtlama ( ) koymak istiyorum .

Rhino Mocks'ın bu işlevi desteklediğini biliyorum, ancak üzerinde çalıştığım proje zaten Moq kullanıyor.


4
Bu soru ve MOQ 3. MOQ 4.8 bir arasında değişen, yan ref parametreler için çok daha iyi bir desteği ile ilgilidir It.IsAny<T>()benzeri eşleştirici ( ref It.Ref<T>.IsAnykurma) desteğe .Callback()ve .Returns()usul şekline uyan özel bir temsilci tipleri yoluyla. Korumalı yöntemler eşit şekilde desteklenir. Bkz . Aşağıdaki cevabım .
stakx - artık

Out parametresini kullanan herhangi bir yöntem için It.Ref <TValue> .Isany kullanabilirsiniz. Örneğin: moq.Setup (x => x.Method (dışarı It.Ref <string> .IsAny) .Returns (TValue);
MikBTC

Yanıtlar:


118

Moq sürüm 4.8 (veya üstü) by-ref parametreleri için çok gelişmiş desteğe sahiptir:

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

Aynı model outparametreler için de geçerlidir.

It.Ref<T>.IsAnyayrıca C # 7 inparametreleri için de çalışır (çünkü bunlar aynı zamanda by-ref'dir).


2
bu çözümdür, bu ref olarak herhangi bir girdiye sahip olmanıza izin verir, aynen ref olmayan girişler için çalışacağı gibi. Bu gerçekten çok güzel geliştirilmiş bir destek
grathad

5
Aynı çözüm işe outyaramıyor, değil mi?
ATD

1
@ATD kısmen evet. Out parametresiyle bir temsilci
bildirin

Alay ettiğiniz işlevin daha fazla argümanı varsa, geri arama imzasının aynı modeli izlemesi gerektiğini belirtmek gerekir (sadece ref / out parametresini değil)
Yoav Feuerstein

320

'Dışarı' için, aşağıdakiler benim için çalışıyor gibi görünüyor.

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

Kur'u çağırıp hatırlar Moq 'beklenenDeğeri' değerine bakar tahmin ediyorum.

İçin ref ben de bir cevap arıyorum.

Aşağıdaki QuickStart kılavuzunu yararlı buldum: https://github.com/Moq/moq4/wiki/Quickstart


7
Bence sorun, yöntemden dışarı / ref parametrelerini atamanın hiçbir yöntemi olmadığıdırSetup
Richard Szalay

1
Ref parametresi atamak için bir çözümüm yok. Bu örnekte 'b' ye "çıkış değeri" değeri atanmıştır. Moq, Kur'a ilettiğiniz İfadeyi yürütmez, analiz eder ve bir çıkış değeri için 'a' sağladığınızı fark eder, bu nedenle 'a' nın mevcut değerine bakar ve sonraki çağrılar için hatırlar.
Craig Celeste

Ayrıca, şu
adresten

9
Mocked arabirim yöntemi kendi başvurulan çıktı değişkenine sahip farklı bir kapsamda yürütüldüğünde (örneğin başka bir sınıfın yönteminde) bu benim için çalışmaz. ancak, tüm senaryoları çözmek çok basittir. Çıkış / ref değerinin açıkça işlenmesi için destek adedi zayıftır (başka biri tarafından söylendiği gibi, yürütme zamanında işlenir).
John K

2
+1: Bu yardımcı bir cevaptır. Ancak: out parametre türü dize gibi bir yapı türü yerine bir sınıfsa - bunun işe yarayacağına inanmıyorum. Bugün denedim. Sahte nesne çağrıyı simüle eder ve "out" parametresi aracılığıyla null değerini döndürür.
azheglov

86

DÜZENLEME : Adedi 4.10'da artık bir out veya ref parametresi olan bir temsilciyi doğrudan Geri Arama işlevine iletebilirsiniz:

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

Bir temsilci tanımlamanız ve başlatmanız gerekir:

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

4.10'dan önceki Adedi sürümü için:

Avner Kashtan, blogunda out parametresini geri aramadan ayarlamaya izin veren bir uzantı yöntemi sunuyor: aramadan Adedi, Geri Aramalar ve Out parametreleri: özellikle zor bir durum

Çözüm hem zarif hem de hacky. Zarif, diğer Moq geri aramalarıyla evde hissedilen akıcı bir sözdizimi sağlar. Ve hacky çünkü bazı dahili Moq API'lerini yansıma yoluyla çağırmaya dayanır.

Yukarıdaki bağlantıda sağlanan uzantı yöntemi benim için derlenmedi, bu yüzden aşağıda düzenlenmiş bir sürüm sağladım. Sahip olduğunuz her sayıda giriş parametresi için bir imza oluşturmanız gerekir; 0 ve 1 sağladım, ancak daha da genişletmek basit olmalı:

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

Yukarıdaki uzantı yöntemiyle, aşağıdaki gibi çıkış parametreleriyle bir arabirimi test edebilirsiniz:

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

.. aşağıdaki Adedi kurulumu ile:

    [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);
    }



Düzenleme : Geçersiz dönüş yöntemlerini desteklemek için sadece yeni aşırı yükleme yöntemleri eklemeniz gerekir:

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

Bu, aşağıdakiler gibi arayüzleri test etmeye izin verir:

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

5
@Wilbert, cevabımı void-return fonksiyonları için ek aşırı yüklerle güncelledim.
Scott Wegner

2
Bu çözümü test paketimizde kullanıyorum ve çalışıyordum. Ancak, Adedi 4.10 güncellemesinden bu yana, artık yapmaz.
Ristogod

2
Görünüşe göre bu işlem github.com/moq/moq4/commit/… . Belki şimdi bunu yapmanın daha iyi bir yolu var mı?
Buji

1
Moq4'teki fyi MethodCall şimdi kurulumun bir özelliği, bu yüzden OutCallbackInternal'ın bağırsakları şu şekilde değişirvar 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, Moq 4.10 güncellemesiyle, artık doğrudan geri arama işlevine bir out veya ref parametresi olan bir delege iletebilirsiniz: Bir delege mock.Setup(x=>x.Method(out d)).Callback(myDelegate).Returns(...);tanımlamanız ve başlatmanız gerekir:...Callback(new MyDelegate((out decimal v)=>v=12m))...;
esteuart

48

Bu Adedi sitesinden belgeler :

// 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);

5
Bu temel olarak Parched'in cevabı ile aynıdır ve aynı sınırlamaya sahiptir, çünkü girişe bağlı olarak çıkış değerini değiştiremez veya ref parametrelerine yanıt veremez.
Richard Szalay

@Richard Szalay, Şunları yapabilirsiniz, ancak ayrı "outString" parametreleriyle ayrı kurulumlar yapmanız gerekir
Sielu


3

Ref parametresi ayarı ile birlikte bir değer döndürmek için bir kod parçası şöyledir:

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

Ardından, taklit edilecek yöntemin imzasıyla eşleşen kendi temsilcinizi beyan edin ve kendi yöntem uygulamanızı sağlayın.

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

Bu, y olarak geçirilecek değişkene erişiminiz olmadığında çalışır mı? DayOfWeek için iki ref bağımsız değişkenleri alan bir işlevi var. Her ikisi de sahte saplama belirli bir güne ayarlamak gerekir ve üçüncü argüman sahte bir veritabanı bağlamı. Ancak delege yöntemi çağrılmaz. Görünüşe göre Moq, "MyMethod" fonksiyonunuzdan geçilen yerel y ile eşleşmeyi bekliyordu. Örneğiniz için bu böyle mi çalışıyor? Teşekkürler.
Greg Veres

3

Billy Jakes awnser'a dayanarak, out parametresiyle tamamen dinamik bir sahte yöntem yaptım. Bunu yararlı bulan herkes için buraya gönderiyorum (muhtemelen sadece gelecekteki beni).

// 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);

2

Eminim Scott'ın çözümü bir noktada işe yaradı,

Ancak özel apislere bakmak için yansımayı kullanmamak için iyi bir argüman. Şimdi kırıldı.

Bir temsilci kullanarak parametreleri ayarlayabildim

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

1

Bu bir çözüm olabilir.

[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());

}

1
Bu temel olarak Parched'in cevabı ile aynıdır ve aynı sınırlamaya sahiptir, çünkü girişe bağlı olarak çıkış değerini değiştiremez veya ref parametrelerine yanıt veremez.
Richard Szalay

1

Basit bir şekilde 'Sahte' sınıfın bir örneğini yaratmadan önce buradaki önerilerin birçoğuyla mücadele ettim. Daha sonra, yöntemin kendisi ile out parametresinin değerini kolayca ayarlayabilirsiniz.


0

Bu öğleden sonra bir saat boyunca bununla mücadele ettim ve hiçbir yerde bir cevap bulamadım. Kendi başıma oynadıktan sonra benim için işe yarayan bir çözüm bulabildim.

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

Buradaki anahtar, mock.SetupAllProperties();sizin için tüm özellikleri saplayacak. Bu, her test durumu senaryosunda çalışmayabilir, ancak tüm eğer yaklaşık oluyor bakım return valueve YourMethodsonra bu irade işi para cezası.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.