การกำหนดพารามิเตอร์ out / ref ใน Moq


293

เป็นไปได้ไหมที่จะกำหนดพารามิเตอร์out/ refโดยใช้ Moq (3.0+)?

ฉันดูที่การใช้งานCallback()แล้ว แต่Action<>ไม่รองรับพารามิเตอร์การอ้างอิงเพราะเป็นข้อมูลทั่วไป ฉันควรเลือกวางข้อ จำกัด ( It.Is) ลงในอินพุตของrefพารามิเตอร์แม้ว่าฉันสามารถทำได้ในการโทรกลับ

ฉันรู้ว่า Rhino Mocks รองรับฟังก์ชั่นนี้ แต่โครงการที่ฉันกำลังใช้งานอยู่ใช้ Moq อยู่แล้ว


4
คำถาม & คำตอบนี้เกี่ยวกับขั้นต่ำ 3 Moq 4.8 ได้รับการปรับปรุงให้ดีขึ้นอย่างมากสำหรับพารามิเตอร์ by-ref ตั้งแต่การIt.IsAny<T>()จับคู่เหมือน ( ref It.Ref<T>.IsAny) เพื่อรองรับการตั้งค่า.Callback()และ.Returns()ผ่านประเภทตัวแทนที่กำหนดเองที่ตรงกับลายเซ็นของวิธีการ วิธีการป้องกันได้รับการสนับสนุนอย่างเท่าเทียมกัน ดูเช่นคำตอบของฉันด้านล่าง
stakx - ไม่สนับสนุน

คุณสามารถใช้ It.Ref <TValue> .Isany สำหรับวิธีการใด ๆ ที่ใช้พารามิเตอร์ ตัวอย่างเช่น: moq.Setup (x => x.Method (out It.Ref <string> .IsAny) .Returns (TValue);
MikBTC

คำตอบ:


118

Moq เวอร์ชัน 4.8 (หรือใหม่กว่า) ได้รับการสนับสนุนที่ดียิ่งขึ้นสำหรับพารามิเตอร์ 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);
}

รูปแบบเดียวกันใช้ได้กับoutพารามิเตอร์

It.Ref<T>.IsAnyยังใช้งานได้กับinพารามิเตอร์C # 7 (เนื่องจากเป็นพารามิเตอร์อ้างอิงด้วย)


2
นี่คือวิธีการแก้ปัญหานี้ช่วยให้คุณมีการป้อนข้อมูลใด ๆ ที่อ้างอิงว่ามันจะทำงานสำหรับการป้อนข้อมูลที่ไม่ใช่ นี่คือการปรับปรุงที่ดีมากอย่างแน่นอน
grathad

5
วิธีแก้ปัญหาแบบเดียวกันนี้ใช้ไม่ได้outใช่ไหม?
ATD

1
@ATD ส่วนหนึ่งใช่ ประกาศผู้รับมอบสิทธิ์ด้วยพารามิเตอร์ out และกำหนดค่าในการโทรกลับด้วยไวยากรณ์จากด้านบน
royalTS

น่ากล่าวถึงว่าถ้าฟังก์ชั่นที่คุณกำลังเยาะเย้ยมีข้อโต้แย้งมากขึ้นแล้วลายเซ็นการโทรกลับควรเป็นไปตามรูปแบบเดียวกัน (ไม่ใช่แค่พารามิเตอร์ ref / out)
Yoav Feuerstein

320

สำหรับ 'ออก' ดูเหมือนว่าต่อไปนี้จะได้ผลสำหรับฉัน

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

ฉันเดาว่า Moq จะดูค่าของ 'ที่คาดหวังมูลค่า' เมื่อคุณเรียกใช้การตั้งค่าและจดจำมัน

สำหรับrefฉันกำลังมองหาคำตอบด้วย

ฉันพบคู่มือ QuickStart ต่อไปนี้มีประโยชน์: https://github.com/Moq/moq4/wiki/Quickstart


7
ผมคิดว่าปัญหาที่ฉันมีก็คือว่าที่เป็นวิธีการที่ไม่มีการกำหนดออก / params อ้างอิงจากวิธีการSetup
ริชาร์ด Szalay

1
ฉันไม่มีวิธีแก้ไขการกำหนดพารามิเตอร์การอ้างอิง ตัวอย่างนี้กำหนดค่าของ "output value" ให้กับ 'b' Moq ไม่ได้ดำเนินการ Expression ที่คุณส่งไปยังการตั้งค่าวิเคราะห์และตระหนักว่าคุณกำลังให้ 'a' สำหรับค่าเอาต์พุตดังนั้นจึงดูค่าปัจจุบันของ 'a' และจดจำการโทรครั้งต่อไป
Craig Celeste

ดูตัวอย่าง out and ref ได้ที่: code.google.com/p/moq/wiki/QuickStart
TrueWill

9
สิ่งนี้จะไม่ทำงานสำหรับฉันเมื่อมีการเรียกใช้วิธีการอินเทอร์เฟซ Mocked ในขอบเขตที่แตกต่างกันซึ่งมีตัวแปรเอาต์พุตที่อ้างอิงของตัวเอง (ตัวอย่างเช่นภายในวิธีของคลาสอื่น) ตัวอย่างที่ให้ไว้ข้างต้นนั้นสะดวก การตั้งค่าจำลอง แต่มันง่ายเกินไปที่จะแก้ไขสถานการณ์ทั้งหมด การสนับสนุนสำหรับการจัดการค่า out / ref อย่างชัดเจนนั้นอ่อนแอใน moq (ตามที่กล่าวไว้โดยบุคคลอื่นซึ่งจัดการในเวลาดำเนินการ)
John K

2
+1: นี่เป็นคำตอบที่เป็นประโยชน์ แต่: หากประเภทพารามิเตอร์ out เป็นคลาสค่อนข้างจะเป็นประเภทบิลด์อินเช่นสตริง - ฉันไม่เชื่อว่าสิ่งนี้จะใช้ได้ พยายามวันนี้ วัตถุจำลองจำลองการโทรและส่งกลับค่า null ผ่านพารามิเตอร์ "out"
azheglov

86

แก้ไข : ในขั้นต่ำ 4.10 ตอนนี้คุณสามารถส่งตัวแทนที่มีพารามิเตอร์ออกหรืออ้างอิงโดยตรงไปยังฟังก์ชั่นการโทรกลับ:

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

คุณจะต้องกำหนดผู้ร่วมประชุมและยกตัวอย่าง:

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

สำหรับ Moq เวอร์ชั่นก่อน 4.10:

Avner Kashtan มีวิธีการขยายในบล็อกของเขาซึ่งอนุญาตให้ตั้งค่าพารามิเตอร์ out จากการเรียกกลับ: พารามิเตอร์Moq, Callbacks และ Out: กรณีขอบที่ยุ่งยากโดยเฉพาะ

การแก้ปัญหามีทั้งความสวยงามและแฮ็ค สง่าในการที่มันให้ไวยากรณ์คล่องแคล่วที่รู้สึกเหมือนอยู่บ้านกับการเรียกกลับ Moq อื่น ๆ และแฮ็คเพราะอาศัยการเรียก Moq APIs ภายในผ่านการสะท้อนกลับ

วิธีการขยายที่มีให้ที่ลิงค์ด้านบนไม่ได้รวบรวมสำหรับฉันดังนั้นฉันจึงได้จัดทำฉบับแก้ไขด้านล่าง คุณจะต้องสร้างลายเซ็นสำหรับพารามิเตอร์ป้อนเข้าแต่ละตัวที่คุณมี ฉันให้ 0 และ 1 แต่การขยายเพิ่มเติมควรง่าย:

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

ด้วยวิธีการขยายข้างต้นคุณสามารถทดสอบอินเทอร์เฟซโดยไม่ใช้พารามิเตอร์เช่น:

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

.. ด้วยการตั้งค่า Moq ต่อไปนี้:

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



แก้ไข : เพื่อสนับสนุนวิธีการคืนค่าเป็นโมฆะคุณเพียงแค่ต้องเพิ่มวิธีการโอเวอร์โหลดใหม่:

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

สิ่งนี้ทำให้สามารถทดสอบอินเตอร์เฟสได้เช่น:

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 ฉันได้อัปเดตคำตอบของฉันพร้อมกับโอเวอร์โหลดเพิ่มเติมสำหรับฟังก์ชั่น void-return
Scott Wegner

2
ฉันใช้โซลูชันนี้ในชุดทดสอบของเราและทำงานได้ดี อย่างไรก็ตามตั้งแต่อัปเดตเป็น Moq 4.10 จะไม่ทำงานอีกต่อไป
Ristogod

2
ดูเหมือนว่ามันจะใช้งานไม่ได้ในgithub.com/moq/moq4/commit/นี้ อาจจะมีวิธีที่ดีกว่าในการทำเช่นนี้ในตอนนี้?
sparkplug

1
fyi ใน Moq4 the MethodCall ตอนนี้เป็นคุณสมบัติของเซ็ตอัพดังนั้นความกล้าของ OutCallbackInternal เหนือการเปลี่ยนแปลงเป็นvar 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 ตอนนี้คุณสามารถส่งผู้รับมอบสิทธิ์ที่มีพารามิเตอร์ out หรือ ref โดยตรงไปยังฟังก์ชัน Callback: mock.Setup(x=>x.Method(out d)).Callback(myDelegate).Returns(...);คุณจะต้องกำหนดผู้รับมอบสิทธิ์และสร้างอินสแตนซ์ของมัน:...Callback(new MyDelegate((out decimal v)=>v=12m))...;
esteuart

48

นี่คือเอกสารจากเว็บไซต์ 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);

5
นี่เป็นพื้นเดียวกันกับคำตอบของ Parched และมีข้อ จำกัด เหมือนกันซึ่งไม่สามารถเปลี่ยนค่าออกได้ขึ้นอยู่กับอินพุตและไม่สามารถตอบสนองต่อพารามิเตอร์อ้างอิงได้
Richard Szalay

@Richard Szalay คุณอาจ แต่คุณจะต้องมีการตั้งค่าแยกต่างหากพร้อมกับพารามิเตอร์ "outString"
Sielu

17

ดูเหมือนว่ามันเป็นไปไม่ได้นอกกรอบ ดูเหมือนว่ามีคนพยายามแก้ปัญหา

ดูโพสต์ฟอรัมนี้ http://code.google.com/p/moq/issues/detail?id=176

คำถามนี้ ตรวจสอบค่าของพารามิเตอร์อ้างอิงด้วย Moq


ขอบคุณสำหรับการยืนยัน จริง ๆ แล้วฉันพบลิงก์ทั้งสองในการค้นหาของฉันแล้ว แต่ยังพบว่า Moq แสดงคุณลักษณะอย่างหนึ่งของมันว่า "สนับสนุนพารามิเตอร์ ref / out" ดังนั้นฉันจึงต้องการให้แน่ใจ
ริชาร์ด Szalay

3

ในการส่งคืนค่าพร้อมกับการตั้งค่าพารามิเตอร์การอ้างอิงนี่คือส่วนหนึ่งของรหัส:

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

จากนั้นประกาศผู้รับมอบสิทธิ์ของคุณเองที่ตรงกับลายเซ็นของวิธีการที่จะถูกเยาะเย้ยและให้การใช้วิธีการของคุณเอง

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

ใช้งานได้เมื่อคุณไม่สามารถเข้าถึงตัวแปรที่จะถูกส่งเป็น y หรือไม่? ฉันมีฟังก์ชั่นที่นำการโต้แย้งสองรายการไปที่ DayOfWeek ฉันต้องตั้งค่าทั้งสองนี้เป็นวันใดวันหนึ่งในต้นขั้วจำลองและอาร์กิวเมนต์ที่สามคือบริบทฐานข้อมูลจำลอง แต่วิธีการมอบหมายไม่ได้ถูกเรียก ดูเหมือนว่า Moq คาดว่าจะตรงกับท้องถิ่น y ที่ถูกส่งผ่าน ot "MyMethod" ของคุณ นั่นเป็นวิธีการทำงานสำหรับตัวอย่างของคุณหรือไม่ ขอบคุณ.
Greg Veres

3

สร้างจาก Billy Jakes awnser ฉันสร้างเมธอดจำลองแบบไดนามิกโดยใช้พารามิเตอร์ out ฉันโพสต์ที่นี่สำหรับทุกคนที่พบว่ามีประโยชน์ (อาจเป็นเพียงอนาคตของฉัน)

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

ฉันแน่ใจว่าโซลูชันของ Scott ทำงานได้ ณ จุดหนึ่ง

แต่มันก็เป็นข้อถกเถียงที่ดีที่ไม่ใช้การไตร่ตรองเพื่อแอบดูที่ apis ส่วนตัว มันพังแล้ว

ฉันสามารถกำหนดพารามิเตอร์โดยใช้ผู้รับมอบสิทธิ์

      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

นี่อาจเป็นวิธีแก้ปัญหา

[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
นี่เป็นพื้นเดียวกันกับคำตอบของ Parched และมีข้อ จำกัด เหมือนกันซึ่งไม่สามารถเปลี่ยนค่าออกได้ขึ้นอยู่กับอินพุตและไม่สามารถตอบสนองต่อพารามิเตอร์อ้างอิงได้
Richard Szalay

1

ฉันต่อสู้กับคำแนะนำมากมายที่นี่ก่อนที่ฉันจะสร้างอินสแตนซ์ของคลาส 'ปลอม' ใหม่ที่ใช้อินเทอร์เฟซอะไรก็ตามที่คุณพยายามจะเยาะเย้ย จากนั้นคุณสามารถตั้งค่าพารามิเตอร์ out ด้วยวิธีของมันเอง


0

ฉันดิ้นรนกับสิ่งนี้เป็นเวลาหนึ่งชั่วโมงในบ่ายวันนี้และไม่สามารถหาคำตอบได้ทุกที่ หลังจากเล่นด้วยตัวเองฉันก็สามารถหาวิธีแก้ปัญหาที่เหมาะกับฉันได้

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

กุญแจสำคัญในที่นี้คือmock.SetupAllProperties();คุณสมบัติของคุณทั้งหมด นี้อาจไม่ทำงานในกรณีที่สถานการณ์การทดสอบทุกครั้ง แต่ถ้าสิ่งที่คุณดูแลเกี่ยวกับการจะได้รับreturn valueของYourMethodแล้วนี้จะทำงานได้ดี

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.