Chỉ định tham số / ref trong Moq


293

Có thể gán một tham số out/ refbằng Moq (3.0+) không?

Tôi đã xem xét việc sử dụng Callback(), nhưng Action<>không hỗ trợ các tham số ref vì nó dựa trên tổng quát. Tôi cũng tốt hơn là muốn đặt một ràng buộc ( It.Is) vào đầu vào của reftham số, mặc dù tôi có thể làm điều đó trong cuộc gọi lại.

Tôi biết rằng Rhino Mocks hỗ trợ chức năng này, nhưng dự án tôi đang thực hiện đã sử dụng Moq.


4
Câu hỏi và trả lời này là về Moq 3. Moq 4.8 có nhiều hỗ trợ được cải thiện hơn cho các tham số phụ, từ một công cụ đối sánh It.IsAny<T>()giống ( ref It.Ref<T>.IsAny) đến hỗ trợ để thiết lập .Callback().Returns()thông qua một loại đại biểu tùy chỉnh phù hợp với chữ ký phương thức. Phương pháp bảo vệ được hỗ trợ như nhau. Xem ví dụ câu trả lời của tôi dưới đây .
stakx - không còn đóng góp

Bạn có thể sử dụng It.Ref <TValue>. Bất kỳ phương thức nào sử dụng tham số out. Ví dụ: moq.Setup (x => x.Method (out It.Ref <string> .IsAny) .Returns (TValue);
MikBTC

Câu trả lời:


117

Phiên bản Moq 4.8 (hoặc phiên bản mới hơn) đã hỗ trợ nhiều cải tiến cho các tham số phụ:

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

Các mô hình tương tự làm việc cho outcác tham số.

It.Ref<T>.IsAnycũng hoạt động cho incác tham số C # 7 (vì chúng cũng là phụ-ref).


2
Đây là giải pháp, điều này cho phép bạn có bất kỳ đầu vào nào dưới dạng ref, chính xác như nó sẽ hoạt động cho đầu vào không ref. Đây thực sự là một hỗ trợ được cải thiện rất tốt
grathad

5
Giải pháp tương tự này không hoạt động outmặc dù, phải không?
ATD

1
@ATD một phần có. Khai báo một đại biểu có tham số out và gán giá trị trong cuộc gọi lại với cú pháp từ trên
royalTS

đáng nói là nếu hàm bạn đang chế giễu có nhiều đối số hơn, thì chữ ký gọi lại phải theo cùng một mẫu (không chỉ là tham số ref / out)
Yoav Feuerstein

320

Đối với 'ra', sau đây dường như làm việc cho tôi.

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

Tôi đoán rằng Moq nhìn vào giá trị của 'tỷ lệ dự kiến' khi bạn gọi Cài đặt và ghi nhớ nó.

Đối với ref, tôi cũng đang tìm kiếm một câu trả lời.

Tôi thấy hướng dẫn QuickStart sau đây hữu ích: https://github.com/Moq/moq4/wiki/Quickstart


7
Tôi nghĩ vấn đề tôi gặp phải là ở chỗ không có phương pháp gán các tham số out / ref từ phương thức đóSetup
Richard Szalay

1
Tôi không có giải pháp để gán tham số ref. Ví dụ này không gán giá trị "giá trị đầu ra" cho 'b'. Moq không thực hiện Biểu thức mà bạn chuyển đến Cài đặt, nó phân tích nó và nhận ra rằng bạn đang cung cấp 'a' cho một giá trị đầu ra, do đó, nó nhìn vào giá trị hiện tại của 'a' và ghi nhớ nó cho các cuộc gọi tiếp theo.
Craig Celeste

Xem thêm các ví dụ ngoài và ref tại: code.google.com/p/moq/wiki/QuickStart
TrueWill

9
Điều này sẽ không hoạt động với tôi khi phương thức giao diện Mocked được thực thi trong một phạm vi khác có biến đầu ra được tham chiếu riêng của nó (ví dụ bên trong phương thức của một lớp khác.) Ví dụ đưa ra ở trên là thuận tiện vì việc thực thi xảy ra trong cùng phạm vi như thiết lập giả, tuy nhiên quá đơn giản để giải quyết tất cả các tình huống. Hỗ trợ xử lý rõ ràng giá trị out / ref yếu trong moq (như đã nói bởi người khác, được xử lý tại thời điểm thực hiện).
John K

2
+1: đây là một câu trả lời hữu ích. Nhưng: nếu loại tham số out là một lớp thay vì kiểu tích hợp như chuỗi - tôi không tin điều này sẽ hoạt động. Đã thử nó ngày hôm nay. Đối tượng giả lập mô phỏng cuộc gọi và trả về null thông qua tham số "out".
azheglov

86

EDIT : Trong Moq 4.10, giờ đây bạn có thể chuyển một đại biểu có tham số out hoặc ref trực tiếp cho hàm Callback:

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

Bạn sẽ phải xác định một đại biểu và khởi tạo nó:

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

Đối với phiên bản Moq trước 4.10:

Avner Kashtan cung cấp một phương thức mở rộng trong blog của mình, cho phép thiết lập tham số out từ một cuộc gọi lại: tham số Moq, Callbacks và Out: trường hợp cạnh đặc biệt khó khăn

Giải pháp là cả thanh lịch và hacky. Thanh lịch ở chỗ nó cung cấp một cú pháp trôi chảy mà cảm thấy như ở nhà với các cuộc gọi lại Moq khác. Và hacky vì nó dựa vào việc gọi một số API Moq nội bộ thông qua sự phản chiếu.

Phương thức mở rộng được cung cấp tại liên kết trên không biên dịch cho tôi, vì vậy tôi đã cung cấp phiên bản chỉnh sửa bên dưới. Bạn sẽ cần tạo một chữ ký cho mỗi số lượng tham số đầu vào bạn có; Tôi đã cung cấp 0 và 1, nhưng việc mở rộng hơn nữa nên đơn giản:

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

Với phương pháp mở rộng ở trên, bạn có thể kiểm tra một giao diện với các tham số như:

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

.. với thiết lập Moq sau:

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



Chỉnh sửa : Để hỗ trợ các phương thức void-return, bạn chỉ cần thêm các phương thức quá tải mới:

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

Điều này cho phép thử nghiệm các giao diện như:

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, tôi đã cập nhật câu trả lời của mình với tình trạng quá tải bổ sung cho các hàm void-return.
Scott Wegner

2
Tôi đã sử dụng giải pháp này trong bộ thử nghiệm của chúng tôi và đã hoạt động. Tuy nhiên, kể từ khi cập nhật lên Moq 4.10, nó không còn hoạt động nữa.
Ristogod

2
Có vẻ như nó đã bị hỏng trong cam kết này github.com/moq/moq4/commit/ . Có lẽ có một cách tốt hơn để làm điều này bây giờ?
sparkplug

1
fyi trong Moq4 Phương thứcCall bây giờ là một tài sản của thiết lập, do đó, sự can đảm của OutCallbackI Internalal thay đổi thànhvar 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, với bản cập nhật lên Moq 4.10, giờ đây bạn có thể chuyển một đại biểu có tham số out hoặc ref trực tiếp đến chức năng Gọi lại: mock.Setup(x=>x.Method(out d)).Callback(myDelegate).Returns(...);Bạn sẽ phải xác định một đại biểu và khởi tạo nó:...Callback(new MyDelegate((out decimal v)=>v=12m))...;
esteuart

48

Đây là tài liệu từ trang 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
Điều này về cơ bản giống như câu trả lời của Parched và có cùng giới hạn, ở chỗ nó không thể thay đổi giá trị ra tùy thuộc vào đầu vào cũng như không thể đáp ứng với các tham số ref.
Richard Szalay

@Richard Szalay, Bạn có thể, nhưng bạn sẽ cần phải có các thiết lập riêng biệt với các tham số "outString" riêng biệt
Sielu

17

Có vẻ như không thể ra khỏi hộp. Có vẻ như ai đó đã cố gắng giải pháp

Xem bài đăng trên diễn đàn này http://code.google.com.vn/p/moq/issues/detail?id=176

câu hỏi này Xác minh giá trị của tham số tham chiếu với Moq


Cám ơn vì đã xác nhận. Tôi thực sự đã tìm thấy hai liên kết đó trong tìm kiếm của mình, nhưng cũng nhận thấy rằng Moq liệt kê một trong những tính năng của nó là "hỗ trợ tham số ref / out", vì vậy tôi muốn chắc chắn.
Richard Szalay

3

Để trả về một giá trị cùng với cài đặt tham số ref, đây là một đoạn mã:

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

Sau đó, khai báo đại biểu của riêng bạn khớp với chữ ký của phương thức được chế giễu và cung cấp phương thức thực hiện phương pháp của riêng bạ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;
         }));
    }

Điều này có hoạt động khi bạn không có quyền truy cập vào biến sẽ được truyền vào dưới dạng y không? Tôi có một hàm có hai đối số ref cho DayOfWeek. Tôi cần đặt cả hai thứ này thành một ngày cụ thể trong sơ đồ giả và đối số thứ ba là bối cảnh cơ sở dữ liệu giả. Nhưng phương thức đại biểu không được gọi. Có vẻ như Moq sẽ mong đợi khớp với chức năng y được truyền qua chức năng "MyMethod" của bạn. Có phải đó là cách làm việc cho ví dụ của bạn? Cám ơn.
Greg Veres

3

Xây dựng trên Billy Jakes awnser, tôi đã thực hiện một phương pháp giả hoàn toàn năng động với một tham số out. Tôi đang đăng bài này lên đây cho bất cứ ai thấy nó hữu ích (có lẽ chỉ là tương lai với tôi).

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

Tôi chắc rằng giải pháp của Scott đã làm việc tại một thời điểm,

Nhưng đó là một lý lẽ tốt cho việc không sử dụng sự phản chiếu để nhìn trộm apis riêng. Bây giờ nó đã bị hỏng.

Tôi đã có thể thiết lập các tham số bằng cách sử dụng một đại biểu

      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

Đây có thể là một giải pháp.

[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
Điều này về cơ bản giống như câu trả lời của Parched và có cùng giới hạn, ở chỗ nó không thể thay đổi giá trị ra tùy thuộc vào đầu vào cũng như không thể đáp ứng với các tham số ref.
Richard Szalay

1

Tôi đã vật lộn với nhiều lời đề nghị ở đây trước khi tôi đơn giản tạo ra một thể hiện của lớp 'Fake' mới thực hiện bất kỳ giao diện nào bạn đang cố gắng giả định. Sau đó, bạn có thể chỉ cần đặt giá trị của tham số out với chính phương thức đó.


0

Tôi đã vật lộn với điều này trong một giờ chiều nay và không thể tìm thấy câu trả lời ở bất cứ đâu. Sau khi tự mình chơi với nó, tôi đã có thể đưa ra một giải pháp hiệu quả cho mình.

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

Chìa khóa ở đây là mock.SetupAllProperties();sẽ khai thác tất cả các thuộc tính cho bạn. Điều này có thể không làm việc trong tất cả các bài kiểm tra trường hợp kịch bản, nhưng nếu tất cả các bạn quan tâm là nhận được return valuecủa YourMethodthì đây tốt sẽ làm việc.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.