簡體   English   中英

在c#中對HTTP請求進行單元測試

[英]Unit testing HTTP requests in c#

我正在編寫一些調用Web服務的代碼,讀取響應並對其執行某些操作。 我的代碼名義上看起來像這樣:

string body = CreateHttpBody(regularExpression, strategy);

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(_url);
request.Method = "POST";
request.ContentType = "text/plain; charset=utf-8";

using (Stream requestStream = request.GetRequestStream())
{
    requestStream.Write(Encoding.UTF8.GetBytes(body), 0, body.Length);
    requestStream.Flush();
}

using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
    byte[] data = new byte[response.ContentLength];

    using (Stream stream = response.GetResponseStream())
    {
        int bytesRead = 0;

        while (bytesRead < data.Length)
        {
            bytesRead += stream.Read(data, bytesRead, data.Length - bytesRead);
        }
    }

    return ExtractResponse(Encoding.UTF8.GetString(data));
}

我實際進行任何自定義操作的唯一部分是ExtractResponseCreateHttpBody方法。 然而,僅僅對這些方法進行單元測試感覺是錯誤的,並且希望其余的代碼能夠正確地組合在一起。 有什么方法可以攔截HTTP請求並提供模擬數據嗎?

編輯此信息現已過期。 使用System.Net.Http.HttpClient庫構建此類代碼要容易得多。

在您的代碼中,您無法攔截對HttpWebRequest的調用,因為您使用相同的方法創建對象。 如果讓另一個對象創建HttpWebRequest ,則可以傳入一個模擬對象並使用它進行測試。

所以不是這樣的:

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(_url);

用這個:

IHttpWebRequest request = this.WebRequestFactory.Create(_url);

在單元測試中,您可以傳入一個創建模擬對象的WebRequestFactory

此外,您可以在單獨的函數中拆分流讀取代碼:

using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
    byte[] data = ReadStream(response.GetResponseStream());
    return ExtractResponse(Encoding.UTF8.GetString(data));
}

這使得可以單獨測試ReadStream()

要進行更多集成測試,您可以設置自己的HTTP服務器,該服務器返回測試數據,並將該服務器的URL傳遞給您的方法。

如果模擬出HttpWebRequest和HttpWebResponse變得過於繁瑣,或者你需要在驗證測試中測試代碼,你從“外部”調用代碼,那么創建虛假服務可能是最好的方法。

我實際上編寫了一個名為MockHttpServer的開源庫來幫助解決這個問題,這使得模擬任何通過HTTP進行通信的外部服務變得非常簡單。

下面是一個使用它與RestSharp一起調用API端點的示例,但HttpWebRequest也可以正常工作。

using (new MockServer(3333, "/api/customer", (req, rsp, prm) => "Result Body"))
{
    var client = new RestClient("http://localhost:3333/");
    var result = client.Execute(new RestRequest("/api/customer", Method.GET));
}

GitHub頁面上有一個相當詳細的自述文件,其中介紹了可供使用它的所有選項,並且庫本身可通過NuGet獲得。

我可能會首先重構代碼,以使其更弱地耦合到實際的HTTP請求。 現在這段代碼似乎做了很多事情。

這可以通過引入抽象來完成:

public interface IDataRetriever
{
    public byte[] RetrieveData(byte[] request);
}

現在,您嘗試進行單元測試的類可以使用Inversion of Control設計模式與實際的HTTP請求分離:

public class ClassToTest
{
    private readonly IDataRetriever _dataRetriever;
    public Foo(IDataRetriever dataRetriever)
    {
        _dataRetriever = dataRetriever;
    }

    public string MethodToTest(string regularExpression, string strategy)
    {
        string body = CreateHttpBody(regularExpression, strategy);
        byte[] result = _dataRetriever.RetrieveData(Encoding.UTF8.GetBytes(body));
        return ExtractResponse(Encoding.UTF8.GetString(result));
    }
}

處理實際的HTTP請求不再是ClassToTest的責任。 它現在已經解耦了。 測試MethodToTest變得非常簡單。

最后一部分顯然是要實現我們引入的抽象:

public class MyDataRetriever : IDataRetriever
{
    private readonly string _url;
    public MyDataRetriever(string url)
    {
        _url = url;
    }

    public byte[] RetrieveData(byte[] request)
    {
        using (var client = new WebClient())
        {
            client.Headers[HttpRequestHeader.ContentType] = "text/plain; charset=utf-8";
            return client.UploadData(_url, request);
        }
    }
}

然后,您可以配置您喜歡的DI框架,將MyDataRetriever實例注入實際應用程序中的ClassToTest類構造函數。

如果你很高興轉移到HttpClient (一個官方的,可移植的,http客戶端庫),那么我前段時間寫了一個可能有助於調用MockHttp的庫 它提供了一個流暢的API,允許您為使用一系列屬性匹配的請求提供響應。

暫無
暫無

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

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