Sử dụng Moq để mô phỏng một phương pháp không đồng bộ cho một bài kiểm tra đơn vị


179

Tôi đang thử nghiệm một phương thức cho một dịch vụ thực hiện APIcuộc gọi Web . Sử dụng một HttpClienthoạt động bình thường tốt cho các thử nghiệm đơn vị nếu tôi cũng chạy dịch vụ web (nằm trong một dự án khác trong giải pháp) cục bộ.

Tuy nhiên, khi tôi kiểm tra các thay đổi của mình, máy chủ bản dựng sẽ không có quyền truy cập vào dịch vụ web nên các thử nghiệm sẽ thất bại.

Tôi đã nghĩ ra cách này để kiểm tra đơn vị của mình bằng cách tạo IHttpClientgiao diện và triển khai phiên bản mà tôi sử dụng trong ứng dụng của mình. Đối với các bài kiểm tra đơn vị, tôi tạo một phiên bản giả được hoàn thành với phương pháp đăng không đồng bộ giả. Đây là nơi tôi gặp vấn đề. Tôi muốn trả lại một OK HttpStatusResultcho thử nghiệm cụ thể này. Đối với một bài kiểm tra tương tự khác, tôi sẽ trả lại một kết quả xấu.

Bài kiểm tra sẽ chạy nhưng sẽ không bao giờ hoàn thành. Nó treo ở chờ đợi. Tôi chưa quen với lập trình không đồng bộ, các đại biểu và chính Moq và tôi đã tìm kiếm SO và google trong một thời gian để học những điều mới nhưng dường như tôi vẫn không thể vượt qua vấn đề này.

Đây là phương pháp tôi đang thử nghiệm:

public async Task<bool> QueueNotificationAsync(IHttpClient client, Email email)
{
    // do stuff
    try
    {
        // The test hangs here, never returning
        HttpResponseMessage response = await client.PostAsync(uri, content);

        // more logic here
    }
    // more stuff
}

Đây là phương pháp kiểm tra đơn vị của tôi:

[TestMethod]
public async Task QueueNotificationAsync_Completes_With_ValidEmail()
{
    Email email = new Email()
    {
        FromAddress = "bob@example.com",
        ToAddress = "bill@example.com",
        CCAddress = "brian@example.com",
        BCCAddress = "ben@example.com",
        Subject = "Hello",
        Body = "Hello World."
    };
    var mockClient = new Mock<IHttpClient>();
    mockClient.Setup(c => c.PostAsync(
        It.IsAny<Uri>(),
        It.IsAny<HttpContent>()
        )).Returns(() => new Task<HttpResponseMessage>(() => new HttpResponseMessage(System.Net.HttpStatusCode.OK)));

    bool result = await _notificationRequestService.QueueNotificationAsync(mockClient.Object, email);

    Assert.IsTrue(result, "Queue failed.");
}

Tôi đang làm gì sai?

Cảm ơn sự giúp đỡ của bạn.

Câu trả lời:


350

Bạn đang tạo một nhiệm vụ nhưng không bao giờ bắt đầu nó, vì vậy nó không bao giờ hoàn thành. Tuy nhiên, đừng chỉ bắt đầu nhiệm vụ - thay vào đó, hãy thay đổi sang sử dụng Task.FromResult<TResult>sẽ cung cấp cho bạn một nhiệm vụ đã hoàn thành:

...
.Returns(Task.FromResult(new HttpResponseMessage(System.Net.HttpStatusCode.OK)));

Lưu ý rằng bạn sẽ không kiểm tra tính không đồng bộ thực tế theo cách này - nếu bạn muốn làm điều đó, bạn cần phải làm thêm một chút để tạo ra một thứ Task<T>mà bạn có thể kiểm soát theo cách chi tiết hơn ... nhưng đó là thứ dành cho Một ngày khác.

Bạn cũng có thể muốn xem xét sử dụng giả IHttpClientthay vì chế giễu mọi thứ - nó thực sự phụ thuộc vào mức độ thường xuyên bạn cần.


2
Cảm ơn rât nhiều. Điều đó đã làm việc tuyệt vời. Tôi đoán nó có lẽ là thứ gì đó đơn giản mà tôi không hiểu.
mvanella

2
Re: Fake IHttpClient, tôi đã xem xét điều đó nhưng tôi cần có thể trả lại các httpStatusCodes khác nhau cho các thử nghiệm khác nhau dựa trên hành vi dự kiến ​​quay lại từ API web và điều này dường như giúp tôi kiểm soát nhiều hơn.
mvanella

3
@mvanella: Có, vì vậy bạn sẽ tạo ra một bản giả có thể trả lại bất cứ thứ gì bạn muốn. Chỉ cần một cái gì đó để suy nghĩ.
Jon Skeet

134
Đối với bất cứ ai tìm thấy điều này bây giờ, Moq 4.2 có một phần mở rộng được gọi ReturnsAysnc, thực hiện chính xác điều này.
Stuart Grassie

3
@legacybass Tôi không thể tìm thấy một liên kết đến bất kỳ tài liệu nào cho nó, mặc dù các tài liệu API nói rằng chúng được xây dựng dựa trên v4.2.1312.1622 đã được phát hành gần như một năm trước. Xem cam kết này đã được thực hiện một vài ngày trước khi phát hành. Về lý do tại sao các tài liệu API không cập nhật ...
Stuart Grassie

16

Đề nghị @Stuart Grassie trả lời ở trên.

var moqCredentialMananger = new Mock<ICredentialManager>();
moqCredentialMananger
                    .Setup(x => x.GetCredentialsAsync(It.IsAny<string>()))
                    .ReturnsAsync(new Credentials() { .. .. .. });

1

Với Mock.Of<...>(...)cho asyncphương pháp mà bạn có thể sử dụng Task.FromResult(...):

var client = Mock.Of<IHttpClient>(c => 
    c.PostAsync(It.IsAny<Uri>(), It.IsAny<HttpContent>()) == Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK))
);
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.