Giá trị trả lại khác nhau lần thứ nhất và lần thứ hai với Moq


262

Tôi có một bài kiểm tra như thế này:

    [TestCase("~/page/myaction")]
    public void Page_With_Custom_Action(string path) {
        // Arrange
        var pathData = new Mock<IPathData>();
        var pageModel = new Mock<IPageModel>();
        var repository = new Mock<IPageRepository>();
        var mapper = new Mock<IControllerMapper>();
        var container = new Mock<IContainer>();

        container.Setup(x => x.GetInstance<IPageRepository>()).Returns(repository.Object);

        repository.Setup(x => x.GetPageByUrl<IPageModel>(path)).Returns(() => pageModel.Object);

        pathData.Setup(x => x.Action).Returns("myaction");
        pathData.Setup(x => x.Controller).Returns("page");

        var resolver = new DashboardPathResolver(pathData.Object, repository.Object, mapper.Object, container.Object);

        // Act
        var data = resolver.ResolvePath(path);

        // Assert
        Assert.NotNull(data);
        Assert.AreEqual("myaction", data.Action);
        Assert.AreEqual("page", data.Controller);
    }

GetPageByUrlchạy hai lần trong tôi DashboardPathResolver, làm thế nào tôi có thể bảo Moq trở lại nulllần đầu tiên và pageModel.Objectlần thứ hai?

Câu trả lời:


454

Với phiên bản Moq mới nhất (4.2.1312.1622), bạn có thể thiết lập chuỗi sự kiện bằng SetupSequence . Đây là một ví dụ:

_mockClient.SetupSequence(m => m.Connect(It.IsAny<String>(), It.IsAny<int>(), It.IsAny<int>()))
        .Throws(new SocketException())
        .Throws(new SocketException())
        .Returns(true)
        .Throws(new SocketException())
        .Returns(true);

Gọi kết nối sẽ chỉ thành công ở lần thử thứ ba và thứ năm nếu không sẽ có ngoại lệ.

Vì vậy, ví dụ của bạn sẽ chỉ là một cái gì đó như:

repository.SetupSequence(x => x.GetPageByUrl<IPageModel>(virtualUrl))
.Returns(null)
.Returns(pageModel.Object);

2
Câu trả lời hay, hạn chế duy nhất là "SetupSequence" không hoạt động với các thành viên được bảo vệ.
Chasefornone

7
Than ôi, SetupSequence()không làm việc với Callback(). Nếu chỉ có vậy, người ta có thể xác minh các cuộc gọi đến phương thức bị chế giễu theo kiểu "máy trạng thái".
urig

@stackunderflow SetupSequencechỉ hoạt động cho hai cuộc gọi nhưng tôi có thể làm gì nếu cần nhiều hơn hai cuộc gọi?
TanvirArjel

@TanvirArjel, không chắc ý của bạn là gì ... SetupSequencecó thể được sử dụng cho một số lượng cuộc gọi tùy ý. Ví dụ đầu tiên tôi đưa ra trả về một chuỗi 5 cuộc gọi.
stackunderflow

@stackunderflow Xin lỗi! Đây là sự hiểu lầm của tôi! Đúng! Bạn đang sửa nó làm việc như mong đợi!
TanvirArjel

115

Các câu trả lời hiện có là rất tốt, nhưng tôi nghĩ rằng tôi sẽ đưa ra giải pháp thay thế chỉ sử dụng System.Collections.Generic.Queuevà không yêu cầu bất kỳ kiến ​​thức đặc biệt nào về khung mô phỏng - vì tôi không có bất kỳ khi nào tôi viết nó! :)

var pageModel = new Mock<IPageModel>();
IPageModel pageModelNull = null;
var pageModels = new Queue<IPageModel>();
pageModels.Enqueue(pageModelNull);
pageModels.Enqueue(pageModel.Object);

Sau đó...

repository.Setup(x => x.GetPageByUrl<IPageModel>(path)).Returns(pageModels.Dequeue);

Cảm ơn. Tôi vừa sửa lỗi chính tả nơi tôi đang mê mẩn pageModel giả thay vì pageModel.Object, vì vậy bây giờ nó cũng sẽ được xây dựng! :)
mo.

3
Câu trả lời là chính xác, nhưng lưu ý rằng điều này sẽ không hoạt động nếu bạn muốn ném Exceptionnhư bạn không thể Enqueue. Nhưng SetupSequencesẽ hoạt động (xem câu trả lời từ @stackunderflow chẳng hạn).
Halvard

4
Bạn phải sử dụng một phương thức được ủy quyền cho Dequeue. Cách viết mẫu, nó sẽ luôn trả về mục đầu tiên trong hàng đợi liên tục, bởi vì dequeue được đánh giá tại thời điểm thiết lập.
Jason Coyne

7
Đó là một đại biểu. Nếu mã chứa Dequeue()thay vì chỉ Dequeue, bạn sẽ đúng.
mo.

31

Thêm một cuộc gọi lại không hiệu quả với tôi, tôi đã sử dụng phương pháp này thay vì http://haacked.com/archive/2009/09/29/moq- resultences.aspx và tôi đã kết thúc bằng một thử nghiệm như thế này:

    [TestCase("~/page/myaction")]
    [TestCase("~/page/myaction/")]
    public void Page_With_Custom_Action(string virtualUrl) {

        // Arrange
        var pathData = new Mock<IPathData>();
        var pageModel = new Mock<IPageModel>();
        var repository = new Mock<IPageRepository>();
        var mapper = new Mock<IControllerMapper>();
        var container = new Mock<IContainer>();

        container.Setup(x => x.GetInstance<IPageRepository>()).Returns(repository.Object);
        repository.Setup(x => x.GetPageByUrl<IPageModel>(virtualUrl)).ReturnsInOrder(null, pageModel.Object);

        pathData.Setup(x => x.Action).Returns("myaction");
        pathData.Setup(x => x.Controller).Returns("page");

        var resolver = new DashboardPathResolver(pathData.Object, repository.Object, mapper.Object, container.Object);

        // Act
        var data = resolver.ResolvePath(virtualUrl);

        // Assert
        Assert.NotNull(data);
        Assert.AreEqual("myaction", data.Action);
        Assert.AreEqual("page", data.Controller);
    }

29

Bạn có thể sử dụng một cuộc gọi lại khi thiết lập đối tượng giả của mình. Hãy xem ví dụ từ Moq Wiki ( http://code.google.com.vn/p/moq/wiki/QuickStart ).

// returning different values on each invocation
var mock = new Mock<IFoo>();
var calls = 0;
mock.Setup(foo => foo.GetCountThing())
    .Returns(() => calls)
    .Callback(() => calls++);
// returns 0 on first invocation, 1 on the next, and so on
Console.WriteLine(mock.Object.GetCountThing());

Thiết lập của bạn có thể trông như thế này:

var pageObject = pageModel.Object;
repository.Setup(x => x.GetPageByUrl<IPageModel>(path)).Returns(() => pageObject).Callback(() =>
            {
                // assign new value for second call
                pageObject = new PageModel();
            });

1
Tôi nhận được null cả hai lần khi tôi làm điều này: var pageModel = new Mock <IPageModel> (); Mô hình IPageModel = null; repository.Setup (x => x.GetPageByUrl <IPageModel> (path)). Trả về (() => model) .Callback (() => {model = pageModel.Object;});
marcus

GetPageByUrl có được gọi hai lần trong phương thức decver.ResolvePath không?
Dan

ResolvePath chứa mã bên dưới nhưng nó vẫn không có giá trị cả hai lần var foo = _reposeective.GetPageByUrl <IPageModel> (virtualUrl); var foo2 = _reposeective.GetPageByUrl <IPageModel> (virtualUrl);
marcus

2
Xác nhận rằng phương pháp gọi lại không hoạt động (thậm chí đã thử trong phiên bản Moq trước đó). Một cách tiếp cận khả thi khác - tùy thuộc vào thử nghiệm của bạn - là chỉ thực hiện lại Setup()cuộc gọi và Return()một giá trị khác.
Kent Boogaart


4

Đạt đến đây cho cùng một loại vấn đề với yêu cầu hơi khác nhau.
Tôi cần nhận các giá trị trả về khác nhau từ giả dựa trên các giá trị đầu vào khác nhau và giải pháp tìm thấy IMO dễ đọc hơn vì nó sử dụng cú pháp khai báo của Moq (linq đến Mocks).

public interface IDataAccess
{
   DbValue GetFromDb(int accountId);  
}

var dataAccessMock = Mock.Of<IDataAccess>
(da => da.GetFromDb(It.Is<int>(acctId => acctId == 0)) == new Account { AccountStatus = AccountStatus.None }
&& da.GetFromDb(It.Is<int>(acctId => acctId == 1)) == new DbValue { AccountStatus = AccountStatus.InActive }
&& da.GetFromDb(It.Is<int>(acctId => acctId == 2)) == new DbValue { AccountStatus = AccountStatus.Deleted });

var result1 = dataAccessMock.GetFromDb(0); // returns DbValue of "None" AccountStatus
var result2 = dataAccessMock.GetFromDb(1); // returns DbValue of "InActive"   AccountStatus
var result3 = dataAccessMock.GetFromDb(2); // returns DbValue of "Deleted" AccountStatus

Đối với tôi (Moq 4.13.0 từ năm 2019 ở đây), nó hoạt động ngay cả với yêu cầu ngắn hơn da.GetFromDb(0) == new Account { ..None.. && da.GetFromDb(1) == new Account { InActive } && ..., không It.Iscần -lambda.
ojdo

3

Các câu trả lời được chấp nhận , cũng như câu trả lời SetupSequence , tay cầm trở về hằng số.

Returns()có một số tình trạng quá tải hữu ích trong đó bạn có thể trả về một giá trị dựa trên các tham số được gửi đến phương thức giả. Dựa trên giải pháp được đưa ra trong câu trả lời được chấp nhận, đây là một phương pháp mở rộng khác cho những tình trạng quá tải đó.

public static class MoqExtensions
{
    public static IReturnsResult<TMock> ReturnsInOrder<TMock, TResult, T1>(this ISetup<TMock, TResult> setup, params Func<T1, TResult>[] valueFunctions)
        where TMock : class
    {
        var queue = new Queue<Func<T1, TResult>>(valueFunctions);
        return setup.Returns<T1>(arg => queue.Dequeue()(arg));
    }
}

Thật không may, sử dụng phương thức này yêu cầu bạn chỉ định một số tham số mẫu, nhưng kết quả vẫn khá dễ đọc.

repository
    .Setup(x => x.GetPageByUrl<IPageModel>(path))
    .ReturnsInOrder(new Func<string, IPageModel>[]
        {
            p => null, // Here, the return value can depend on the path parameter
            p => pageModel.Object,
        });

Tạo quá tải đối với phương pháp mở rộng với nhiều tham số ( T2, T3, vv) nếu cần thiết.

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.