簡體   English   中英

Moq:如何使用Nunit和內部HttpClient測試一個類?

[英]Moq: How to test a class using Nunit with an internal HttpClient?

我在nUnit中運行我的測試,通常我可以模擬出依賴項,然后返回某些值或拋出錯誤。

我有一個類作為內部HttpClient,我想測試該類,我有什么選擇。

這是我的代碼,它不完整,以免泛濫消息。 正如您所看到的,我在內部使用HttpClient而不是作為依賴項注入。 該類拋出了許多自定義異常,我想Moq這些,否則我需要傳遞真實的用戶名和密碼,這將給我提供我需要拋出異常的狀態代碼。

有人有想法嗎? 如果我不能模擬httpclient,那么我永遠不會測試我的類,它會引發異常。

我是否真的必須將HttpClient更改為對構造函數的依賴?

public bool ItemsExist(string itemValue)
{

    var relativeUri = string.Format(UrlFormatString, itemValue.ToUpper());

    var uri = new Uri(new Uri(this.baseUrl), relativeUri);

    using (var client = new HttpClient())
    {
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", this.encodedCredentials);
        client.DefaultRequestHeaders.Accept.Add(
            new MediaTypeWithQualityHeaderValue("application/json"));

        var response = client.GetAsync(uri).Result;

        switch (response.StatusCode)
        {
            case HttpStatusCode.Unauthorized:
                // DO something here
                throw new CustomAuthorizationException();

            case HttpStatusCode.Forbidden:
                throw new CustomAuthenticationException();

        }

        return true;

讓我建議一個更簡單的解決方案,而不需要抽象/包裝httpclient,我相信與mocking框架完美配合。

您需要為假HttpMessageHandler創建一個類,如下所示:

public class FakeHttpMessageHandler : HttpMessageHandler
{
    public virtual HttpResponseMessage Send(HttpRequestMessage request)
    {
        throw new NotImplementedException("Rember to setup this method with your mocking framework");
    }

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
    {
        return Task.FromResult(Send(request));
    }
}

在實例化HttpClient時可以使用這樣創建的HttpMessageHandler:

var msgHandler = new Mock<FakeHttpMessageHandler>() { CallBase = true };
var fakeclient = new HttpClient(msgHandler.Object);

你可以設置方法(這里使用Moq):

msgHandler.Setup(t => t.Send(It.Is<HttpRequestMessage>(
            msg =>
                    msg.Method == HttpMethod.Post &&
                    msg.RequestUri.ToString() == "http://test.te/item/123")))
                    .Returns(new HttpResponseMessage(System.Net.HttpStatusCode.NotFound));

您現在可以在必要時使用fakeclient。

你無法對它進行單元測試。 就像你提到的那樣:HttpClient是一個依賴項,因此它應該被注入。

就個人而言,我會創建自己的IHttpClient接口,由HttpClientWrapper實現,它包裝了System.Net.HttpClient 然后, IHttpClient將作為依賴項傳遞給對象的構造函數。

如下, HttpClientWrapper無法進行單元測試。 但是,我會編寫幾個集成測試來確保包裝器編寫得很好。

編輯

IHttpClient不一定是HttpClient的“有效”接口。 它只需要一個適合您需求的界面。 只要你想它可以有很多盡可能少的方法。

想象一下:HttpClient允許你做很多事情。 但是在你的項目中,你只是調用GetAsync(uri).Result方法,沒有別的。

在這種情況下,您將編寫以下接口和實現:

interface IHttpClient
{
    HttpResponseMessage Get(string uri);
}

class HttpClientWrapper : IHttpClient
{
    private readonly HttpClient _client;

    public HttpClientWrapper(HttpClient client)
    {
        _client = client;
    }


    public HttpResponseMessage Get(string uri)
    {
        return _client.GetAsync(new Uri(uri)).Result;
    }
}

因此,正如我之前所說,界面只需滿足您的需求。 您不必環繞WHOLE HttpClient類。

顯然,你會像這樣moq你的對象:

var clientMock = new Mock<IHttpClient>();
//setup mock
var myobj = new MyClass(clientMock.object);

並創建一個實際的對象:

var client = new HttpClientWrapper(new HttpClient());
var myobj = new MyClass(client );

EDIT2

哦! 並且不要忘記IHttpClient也應該擴展IDisposable接口,非常重要!

另一個選擇是使用Flurl [披露:我是作者],一個用於構建和調用URL的庫。 它包括測試幫助程序 ,使得偽造所有HTTP非常容易。 不需要包裝器接口。

對於初學者,您的HTTP代碼本身看起來像這樣:

using Flurl;
using Flurl.Http;

...

try {
    var response = this.baseUrl
        .AppendPathSegment(relativeUri)
        .WithBasicAuth(username, password)
        .WithHeader("Accept", "application/json")
        .GetAsync().Result;

    return true;
}
catch (FlurlHttpException ex) {
    // Flurl throws on unsuccessful responses. Null-check ex.Response,
    // then do your switch on ex.Response.StatusCode.
}

現在為測試樂趣:

using Flurl.Http.Testing;

...

[Test]
public void ItemsExists_SuccessResponse() {
    // kick Flurl into test mode - all HTTP calls will be faked and recorded
    using (var httpTest = new HttpTest()) {
        // arrange
        test.RespondWith(200, "{status:'ok'}");
        // act
        sut.ItemExists("blah");
        // assert
        test.ShouldHaveCalled("http://your-url/*");
    }
}

在NuGet上獲取它:

PM> Install-Package Flurl.Http

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM