Moq: Làm thế nào để có được một tham số được truyền cho một phương thức của dịch vụ giả


168

Tưởng tượng lớp học này

public class Foo {

    private Handler _h;

    public Foo(Handler h)
    {
        _h = h;
    }

    public void Bar(int i)
    {
        _h.AsyncHandle(CalcOn(i));
    }

    private SomeResponse CalcOn(int i)
    {
        ...;
    }
}

Mo (q) cking Handler trong một bài kiểm tra của Foo, làm thế nào tôi có thể kiểm tra những gì Bar()đã qua _h.AsyncHandle?


Ý của bạn là "AsyncHandle" (thêm "n")? Và bạn có thể đăng mã cho Handler hoặc chỉ định tên loại đủ điều kiện nếu đó là loại tiêu chuẩn?
TrueWill

Bạn có thể hiển thị bài kiểm tra bộ xương của bạn để cho thấy những gì bạn đang suy nghĩ? Mặc dù tôi đánh giá cao điều đó từ phía bạn, nhưng rõ ràng từ phía chúng tôi, có vẻ như ai đó đã không dành thời gian để trả lời câu hỏi mà không cần trả lời suy đoán dài.
Ruben Bartelink

1
Không có Foo hay Bar () hay bất cứ thứ gì như thế này. Nó chỉ là một số mã trình diễn để hiển thị tình huống tôi đang ở mà không bị phân tâm từ các chi tiết cụ thể. Và tôi chỉ nhận được câu trả lời, tôi đã hy vọng có được.
Jan

Câu trả lời:


282

Bạn có thể sử dụng phương thức Mock.Callback:

var mock = new Mock<Handler>();
SomeResponse result = null;
mock.Setup(h => h.AnsyncHandle(It.IsAny<SomeResponse>()))
    .Callback<SomeResponse>(r => result = r);

// do your test
new Foo(mock.Object).Bar(22);
Assert.NotNull(result);

Nếu bạn chỉ muốn kiểm tra một cái gì đó đơn giản trên đối số được thông qua, bạn cũng có thể thực hiện trực tiếp:

mock.Setup(h => h.AnsyncHandle(It.Is<SomeResponse>(response => response != null)));

36
Một lưu ý phụ, nếu bạn có nhiều đối số cho hàm của mình, bạn cần chỉ định tất cả các loại trong Callback<>()phương thức Moq chung . Chẳng hạn, nếu phương thức của bạn có định nghĩa Handler.AnsyncHandle(string, SomeResponse), bạn sẽ cần /* ... */.Callback<string, SomeResponse>(r => result = r);. Tôi đã không tìm thấy điều này được nêu rõ ràng ở nhiều nơi, vì vậy tôi đoán rằng tôi sẽ thêm nó ở đây.
Frank Bryce

12
Chỉ muốn sửa @Frank trong trường hợp bạn chưa thấy câu trả lời của @JavaJudt. Cách đúng để có được hai đối số là:/* ... */.Callback<string, SomeResponse>((s1, s2) => { str1 = s1; result = s2});
renatogbp

2
Bạn cũng có thể đạt được điều tương tự bằng cách chỉ cần khai báo các loại đối số trong biểu thức lambda, như vậy:.Callback((string s1, SomeResponse s2) => /* stuff */ )
jb637

1
Bạn có thể vui lòng cập nhật phản hồi của mình để bao gồm một tham chiếu đến trình Capture.Intrợ giúp tích hợp không?
cao

29

Câu trả lời của Gamlor có hiệu quả với tôi, nhưng tôi nghĩ rằng tôi sẽ mở rộng nhận xét của John Carpenter vì tôi đang tìm kiếm một giải pháp liên quan đến nhiều hơn một tham số. Tôi cho rằng những người khác vấp ngã trên trang này có thể ở trong một tình huống tương tự. Tôi tìm thấy thông tin này trong tài liệu Moq .

Tôi sẽ sử dụng ví dụ của Gamlor, nhưng hãy giả vờ phương thức AsyncHandle có hai đối số: a stringvà một SomeResponseđối tượng.

var mock = new Mock<Handler>();
string stringResult = string.Empty;
SomeResponse someResponse = null;
mock.Setup(h => h.AsyncHandle(It.IsAny<string>(), It.IsAny<SomeResponse>()))
    .Callback<string, SomeResponse>((s, r) => 
    {
        stringResult = s;
        someResponse = r;
    });

// do your test
new Foo(mock.Object).Bar(22);
Assert.AreEqual("expected string", stringResult);
Assert.IsNotNull(someResponse);

Về cơ bản, bạn chỉ cần thêm một kiểu khác It.IsAny<>()với kiểu thích hợp, thêm kiểu khác vào Callbackphương thức và thay đổi biểu thức lambda cho phù hợp.


22

Phương thức gọi lại chắc chắn sẽ hoạt động, nhưng nếu bạn đang thực hiện điều này trên một phương thức có nhiều tham số thì nó có thể hơi dài dòng. Đây là một cái gì đó mà tôi đã sử dụng để loại bỏ một số nồi hơi.

var mock = new Mock<Handler>();

// do your test   
new Foo(mock.Object).Bar(22);

var arg = new ArgumentCaptor<SomeResponse>();
mock.Verify(h => h.AsyncHandle(arg.Capture()));
Assert.NotNull(arg.Value);

Đây là nguồn cho ArgumentCaptor:

public class ArgumentCaptor<T>
{
    public T Capture()
    {
        return It.Is<T>(t => SaveValue(t));
    }

    private bool SaveValue(T t)
    {
        Value = t;
        return true;
    }

    public T Value { get; private set; }
}

21

Câu trả lời của Gamlor hoạt động, nhưng một cách khác để làm điều đó (và một cách mà tôi cho là biểu cảm hơn trong bài kiểm tra) là ...

var mock = new Mock<Handler>();
var desiredParam = 47; // this is what you want to be passed to AsyncHandle
new Foo(mock.Object).Bar(22);
mock.Verify(h => h.AsyncHandle(desiredParam), Times.Once());

Xác minh là rất mạnh mẽ và đáng để dành thời gian để làm quen.


15
Cách tiếp cận đó là tốt nếu bạn chỉ muốn kiểm tra nếu một phương thức được gọi với một tham số đã biết. Trong trường hợp tham số chưa được tạo tại thời điểm viết bài kiểm tra (ví dụ: đơn vị được đề cập sẽ tạo tham số bên trong), thì Callback cho phép bạn nắm bắt và thẩm vấn điều này, trong khi phương pháp của bạn sẽ không.
Michael

1
Tôi cần lưu trữ giá trị đã qua bởi vì tôi cần xác minh rằng tất cả một tập hợp các đối tượng đã được thông qua.
MrFox

Ngoài ra, đôi khi tham số là một thực thể và bạn muốn các xác nhận riêng cho từng trường trong thực thể. Cách tốt nhất để làm điều đó là nắm bắt thông số, thay vì sử dụng Xác minh và trình so khớp.
Kevin Wong

11

Thay thế cũng là sử dụng Capture.Intính năng của moq. Đây là moqtính năng OOTB cho phép ghi lại đối số trong bộ sưu tập.

//Arrange
var args = new List<SomeResponse>();
mock.Setup(h => h.AnsyncHandle(Capture.In(args)));

//Act
new Foo(mock.Object).Bar(22);

//Assert
//... assert args.Single() or args.First()

1
Câu trả lời chính xác! Tôi không biết về các lớp Moq.Capture và Moq.CaptureMatch cho đến khi thấy câu trả lời này. Capture là một thay thế tốt hơn cho CallbackIMO. Vì bạn sử dụng Capture trực tiếp trong danh sách tham số, nên sẽ ít gặp sự cố hơn khi tái cấu trúc danh sách tham số của phương thức và do đó làm cho các kiểm tra ít giòn hơn. Với Callback, bạn phải giữ các tham số được truyền trong Thiết lập đồng bộ với các tham số loại được sử dụng cho Callback và điều này chắc chắn đã gây ra sự cố cho tôi trong quá khứ.
Justin Holzer

7

Bạn có thể sử dụng It.Is<TValue>()diêm.

var mock = new Mock<Handler>();
new Foo(mock.Object).Bar(22);
mock.Verify(h => h.AsyncHandle(It.Is<SomeResponse>(r => r != null )));

2

Điều này cũng hoạt động:

Mock<InterfaceThing> mockedObject = new Mock<InterfaceThing>();
var objectParameter = mockedObject.Invocations[1].Arguments[0] as ObjectParameter;

2

Rất nhiều câu trả lời hay ở đây! Sử dụng tính năng Moq bên ngoài hộp cho đến khi bạn cần xác nhận về một số tham số lớp được truyền cho các phụ thuộc của bạn. Tuy nhiên, nếu bạn kết thúc trong tình huống đó, tính năng Moq Confirm với It. Các trình so khớp không thực hiện tốt việc cô lập lỗi kiểm tra và cách quay trở lại / Gọi lại để thêm các dòng mã không cần thiết vào kiểm tra của bạn (và bài kiểm tra dài là không có đối với tôi).

Đây là một ý chính: https://gist.github.com/Jacob-McKay/8b8d41ebb9565f5fca23654fd944ac6b với phần mở rộng Moq (4.12) mà tôi đã viết để đưa ra một cách khai báo hơn về các đối số được đưa ra cho các đối số được đưa ra để xác nhận. Đây là phần xác minh hiện tại trông như thế nào:

        mockDependency
            .CheckMethodWasCalledOnce(nameof(IExampleDependency.PersistThings))
            .WithArg<InThing2>(inThing2 =>
            {
                Assert.Equal("Input Data with Important additional data", inThing2.Prop1);
                Assert.Equal("I need a trim", inThing2.Prop2);
            })
            .AndArg<InThing3>(inThing3 =>
            {
                Assert.Equal("Important Default Value", inThing3.Prop1);
                Assert.Equal("I NEED TO BE UPPER CASED", inThing3.Prop2);
            });

Tôi sẽ bị đánh cắp nếu Moq cung cấp một tính năng hoàn thành điều tương tự trong khi là tuyên bố và cung cấp sự cô lập thất bại này. Ngón tay đan chéo!


1
Tôi thích điều này. Xác minh Moq cạnh tranh với Khẳng định của xUnit để có thẩm quyền thực hiện các xác nhận. Điều đó không cảm thấy đúng về phần Moq của thiết lập. Thiết lập tính năng It.Is cũng hỗ trợ các biểu thức không.
Thomas

"Moq cạnh tranh với Assert của xUnit để có thẩm quyền thực hiện các khẳng định" - @Thomas nói. Tôi sẽ thêm rằng nó sẽ mất sự cạnh tranh. Moq rất tốt trong việc cho bạn biết liệu có cuộc gọi phù hợp hay không, nhưng các xác nhận cụ thể cung cấp cho bạn thông tin tốt hơn nhiều. Hạn chế chính đối với cách tiếp cận của tôi là mất an toàn loại và kiểm tra thứ tự các tham số. Tôi đã tìm kiếm một sự cải thiện trong thời gian này, hy vọng có một ninja C # ngoài kia có thể hack một ví dụ cùng nhau! Nếu không, nếu tôi tìm thấy một cách tôi sẽ cập nhật này.
Jacob McKay
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.