Birim testlerinde HttpClient ile alay etme


111

Birim testlerinde kullanılmak üzere kodumu sarmaya çalışırken bazı sorunlar yaşıyorum. Sorun bu. IHttpHandler arayüzüne sahibim:

public interface IHttpHandler
{
    HttpClient client { get; }
}

Ve onu kullanan sınıf, HttpHandler:

public class HttpHandler : IHttpHandler
{
    public HttpClient client
    {
        get
        {
            return new HttpClient();
        }
    }
}

Ve sonra, istemci uygulamasını enjekte etmek için simpleIOC kullanan Connection sınıfı:

public class Connection
{
    private IHttpHandler _httpClient;

    public Connection(IHttpHandler httpClient)
    {
        _httpClient = httpClient;
    }
}

Ve sonra bu sınıfa sahip bir birim test projem var:

private IHttpHandler _httpClient;

[TestMethod]
public void TestMockConnection()
{
    var client = new Connection(_httpClient);

    client.doSomething();  

    // Here I want to somehow create a mock instance of the http client
    // Instead of the real one. How Should I approach this?     

}

Şimdi açıkçası, Bağlantı sınıfında arka ucumdan veri (JSON) alacak yöntemlere sahip olacağım. Ancak, bu sınıf için birim testleri yazmak istiyorum ve açıkçası gerçek arka uca karşı testler yazmak istemiyorum, bunun yerine alay konusu. Büyük bir başarı olmadan buna iyi bir cevap vermeyi denedim. Moq'u daha önce alay etmek için kullanabilirim ve kullandım, ancak asla httpClient gibi bir şeyde kullanmadım. Bu soruna nasıl yaklaşmalıyım?

Şimdiden teşekkürler.


1
HttpClientArayüzünüzde bir ortaya çıkarmak sorunun olduğu yerdir. Müşterinizi HttpClientsomut sınıfı kullanmaya zorluyorsunuz . Bunun yerine, bir göstermelidir soyutlama arasında HttpClient.
Mike Eason

Bunu biraz daha derinlemesine açıklayabilir misin? Bağlantı sınıfları oluşturucusunu nasıl oluşturmalıyım çünkü diğer sınıflarda herhangi bir HttpClient bağımlılığı istemiyorum, bu da Connection sınıfını kullanır. Örneğin, Connection yapıcısında concerete HttpClient geçirmek istemiyorum çünkü bu, Connection kullanan diğer tüm sınıfları HttpClient'e bağımlı hale getirir mi?
tjugg

İlgisiz, ne google yaptın? Görünüşe göre mockhttp bazı SEO iyileştirmeleri kullanabilir.
Richard Szalay

@Mike - cevabımda belirtildiği gibi, gerçekten HttpClient'i soyutlamaya gerek yok. Olduğu gibi mükemmel bir şekilde test edilebilir. Bu yöntemi kullanan arka uçsuz test paketlerine sahip çok sayıda projem var.
Richard Szalay

Yanıtlar:


37

Arayüzünüz somut HttpClientsınıfı ortaya çıkarır , bu nedenle bu arayüzü kullanan tüm sınıflar ona bağlıdır, bu da alay edilemeyeceği anlamına gelir.

HttpClientherhangi bir arayüzden miras almadığı için kendi yazmanız gerekecektir. Dekoratör benzeri bir model öneriyorum :

public interface IHttpHandler
{
    HttpResponseMessage Get(string url);
    HttpResponseMessage Post(string url, HttpContent content);
    Task<HttpResponseMessage> GetAsync(string url);
    Task<HttpResponseMessage> PostAsync(string url, HttpContent content);
}

Ve sınıfınız şöyle görünecek:

public class HttpClientHandler : IHttpHandler
{
    private HttpClient _client = new HttpClient();

    public HttpResponseMessage Get(string url)
    {
        return GetAsync(url).Result;
    }

    public HttpResponseMessage Post(string url, HttpContent content)
    {
        return PostAsync(url, content).Result;
    }

    public async Task<HttpResponseMessage> GetAsync(string url)
    {
        return await _client.GetAsync(url);
    }

    public async Task<HttpResponseMessage> PostAsync(string url, HttpContent content)
    {
        return await _client.PostAsync(url, content);
    }
}

Tüm bunların içindeki nokta HttpClientHandler, kendi kendini yaratmasıdır, HttpClientdaha sonra elbette uygulayan birden fazla sınıf oluşturabilirsiniz.IHttpHandler farklı şekillerde .

Bu yaklaşımla ilgili ana sorun, yalnızca başka bir sınıfta yöntemleri çağıran bir sınıfı etkili bir şekilde yazıyor olmanızdır, ancak miras alanHttpClient ( Nkosi örneğine bakın , bu benimkinden çok daha iyi bir yaklaşım). HttpClientAlay edebileceğiniz bir arayüz olsaydı hayat çok daha kolay olurdu , maalesef öyle değil.

Ancak bu örnek altın bilet değildir . IHttpHandlerhala güveniyorHttpResponseMessageSystem.Net.Http ad alanına ait olana , bu nedenle, dışında başka uygulamalara ihtiyacınız varsa HttpClient, yanıtlarını HttpResponseMessagenesnelere dönüştürmek için bir tür eşleme yapmanız gerekecektir . Bu, elbette, yalnızca birden fazla uygulama kullanmanız gerekiyorsa bir sorundur .IHttpHandler ancak öyle görünmüyor, bu yüzden dünyanın sonu değil, düşünülmesi gereken bir şey.

Her neyse, soyutlanmış IHttpHandlersomut HttpClientsınıf hakkında endişelenmenize gerek kalmadan basitçe alay edebilirsiniz .

Eşzamansız olmayan yöntemleri test etmenizi öneririm , çünkü bunlar hala eşzamansız yöntemleri çağırır, ancak eşzamansız yöntemleri birim testi konusunda endişelenmenize gerek kalmaz , buraya bakın


Bu gerçekten soruma cevap veriyor. Nkosis cevabı da doğru, bu yüzden hangisini cevap olarak kabul etmem gerektiğinden emin değilim, ama bununla devam edeceğim. Çabalarınız için
ikinize

@tjugg Yardımcı olmaktan memnunum. Faydalı bulursanız, yanıtları oylamaktan çekinmeyin.
Nkosi

3
Bu cevap ile Nkosi'ninki arasındaki temel farkın, bunun çok daha ince bir soyutlamadır. İnce muhtemelen mütevazı bir nesne
Ben Aaronson

228

HttpClient'in genişletilebilirliği, yapıcıya HttpMessageHandleriletilen değerdedir . Amacı, platforma özel uygulamalara izin vermektir, ancak bununla da dalga geçebilirsiniz. HttpClient için bir dekoratör sarıcı oluşturmaya gerek yoktur.

Moq kullanmak yerine bir DSL tercih ederseniz, GitHub / Nuget'te işleri biraz daha kolaylaştıran bir kitaplığım var: https://github.com/richardszalay/mockhttp

var mockHttp = new MockHttpMessageHandler();

// Setup a respond for the user api (including a wildcard in the URL)
mockHttp.When("http://localost/api/user/*")
        .Respond("application/json", "{'name' : 'Test McGee'}"); // Respond with JSON

// Inject the handler or client into your application code
var client = new HttpClient(mockHttp);

var response = await client.GetAsync("http://localhost/api/user/1234");
// or without async: var response = client.GetAsync("http://localhost/api/user/1234").Result;

var json = await response.Content.ReadAsStringAsync();

// No network connection required
Console.Write(json); // {'name' : 'Test McGee'}

1
Yani MockHttpMessageHandler'ı mesaj işleyici Httphandler sınıfı olarak geçebilir miyim? Veya bunu kendi projelerinize nasıl uyguladınız
tjugg

2
Harika cevap ve başlangıçta bilemeyeceğim bir şey. HttpClient ile çalışmayı o kadar da kötü değil.
Bealer

6
Hastayı enjekte etmekle uğraşmak istemeyen ancak yine de kolay test edilebilirlik isteyen insanlar için, başarmak önemsizdir. Sadece bir alanla değiştirin var client = new HttpClient()ve ayarlayın var client = ClientFactory()ve internal static Func<HttpClient> ClientFactory = () => new HttpClient();test düzeyinde bu alanı yeniden yazabilirsiniz.
Chris Marisic

3
@ChrisMarisic, enjeksiyonun yerini alacak bir servis konumu biçimi öneriyorsunuz. Servis yeri iyi bilinen bir anti-modeldir, bu nedenle imho enjeksiyon tercih edilir.
MarioDS

2
@MarioDS ve ne olursa olsun, bir HttpClient örneğini hiç enjekte etmemelisiniz . Bunu o zaman bir enjekte edilmelidir için constructer enjeksiyonu kullanılarak ölü seti ise HttpClientFactoryolduğu gibi Func<HttpClient>. HttpClient'i bağımlılık değil, tamamen bir uygulama ayrıntısı olarak gördüğüm için, yukarıda gösterdiğim gibi statiği kullanacağım. İç kısımları değiştiren testlerde tamamen iyiyim. Saf-izm'i önemsiyorsam, tam sunucuları kaldırıp canlı kod yollarını test edeceğim. Herhangi bir tür taklit kullanmak, gerçek davranışı değil yaklaşık davranışı kabul ettiğiniz anlamına gelir.
Chris Marisic

40

En iyi yaklaşımın HttpClient'i paketlemek yerine HttpMessageHandler ile dalga geçmek olduğu yönündeki diğer yanıtların bazılarına katılıyorum. Bu cevap, HttpClient'i hala enjekte etmesi ve bunun bir singleton olmasına veya bağımlılık enjeksiyonu ile yönetilmesine izin vermesi açısından benzersizdir.

HttpClient'in bir kez başlatılması ve bir uygulamanın ömrü boyunca yeniden kullanılması amaçlanmıştır.

( Kaynak ).

HttpMessageHandler ile alay etmek biraz yanıltıcı olabilir çünkü SendAsync korumalı. İşte xunit ve Moq kullanarak eksiksiz bir örnek.

using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Moq;
using Moq.Protected;
using Xunit;
// Use nuget to install xunit and Moq

namespace MockHttpClient {
    class Program {
        static void Main(string[] args) {
            var analyzer = new SiteAnalyzer(Client);
            var size = analyzer.GetContentSize("http://microsoft.com").Result;
            Console.WriteLine($"Size: {size}");
        }

        private static readonly HttpClient Client = new HttpClient(); // Singleton
    }

    public class SiteAnalyzer {
        public SiteAnalyzer(HttpClient httpClient) {
            _httpClient = httpClient;
        }

        public async Task<int> GetContentSize(string uri)
        {
            var response = await _httpClient.GetAsync( uri );
            var content = await response.Content.ReadAsStringAsync();
            return content.Length;
        }

        private readonly HttpClient _httpClient;
    }

    public class SiteAnalyzerTests {
        [Fact]
        public async void GetContentSizeReturnsCorrectLength() {
            // Arrange
            const string testContent = "test content";
            var mockMessageHandler = new Mock<HttpMessageHandler>();
            mockMessageHandler.Protected()
                .Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
                .ReturnsAsync(new HttpResponseMessage {
                    StatusCode = HttpStatusCode.OK,
                    Content = new StringContent(testContent)
                });
            var underTest = new SiteAnalyzer(new HttpClient(mockMessageHandler.Object));

            // Act
            var result = await underTest.GetContentSize("http://anyurl");

            // Assert
            Assert.Equal(testContent.Length, result);
        }
    }
}

1
Bunu gerçekten beğendim. mockMessageHandler.Protected()Katildi. Bu örnek için teşekkürler. Kaynağı hiç değiştirmeden testin yazılmasına izin verir.
tyrion

1
Bilginize, Moq 4.8 destekler kuvvetle korunan üyelerin alay yazdığınız - github.com/Moq/moq4/wiki/Quickstart
Richard Szalay

2
Bu harika görünüyor. Ayrıca Moq, ReturnsAsync'i destekler, böylece kod şöyle görünür.ReturnsAsync(new HttpResponseMessage {StatusCode = HttpStatusCode.OK, Content = new StringContent(testContent)})
kord

Teşekkürler @kord, bunu cevaba
ekledim

3
"SandAsync" in bazı parametrelerle çağrıldığını doğrulamanın bir yolu var mı? Kullanmayı denedim ... Korumalı (). Doğrula (...), ancak zaman uyumsuz yöntemlerle çalışmıyor gibi görünüyor.
Rroman

29

Bu yaygın bir sorudur ve ağırlıklı olarak HttpClient ile dalga geçme yeteneğini isteyen taraftaydım, ancak sonunda HttpClient ile dalga geçmemeniz gerektiğini anladım. Bunu yapmak mantıklı görünüyor, ancak açık kaynak kitaplıklarında gördüğümüz şeyler tarafından beynimizin yıkandığını düşünüyorum.

Tek başına test edebilmemiz için kodumuzda alay ettiğimiz sık sık "Müşteriler" görüyoruz, böylece aynı prensibi HttpClient'e de otomatik olarak uygulamaya çalışıyoruz. HttpClient aslında çok şey yapar; HttpMessageHandler için bir yönetici olarak düşünebilirsiniz, bu yüzden bununla dalga geçmek istemezsiniz ve bu yüzden hala bir arayüze sahip değildir. Birim testi veya hizmetlerinizi tasarlamak için gerçekten ilgilendiğiniz kısım HttpMessageHandler'dır çünkü yanıtı döndüren şey budur ve bununla alay edebilirsiniz .

HttpClient'e muhtemelen daha büyük bir anlaşma gibi davranmaya başlamanız gerektiğine de işaret etmeye değer. Örneğin: Yeni HttpClients uygulamasını minimumda tutun. Onları tekrar kullanın, yeniden kullanılmak üzere tasarlandılar ve eğer yaparsanız çok daha az kaynak kullanırlar. Eğer ona daha büyük bir anlaşma gibi davranmaya başlarsanız, onunla dalga geçmeyi istemek çok daha yanlış hissedecek ve şimdi mesaj işleyici sizin enjekte ettiğiniz şey olmaya başlayacak, müşteri değil.

Başka bir deyişle, bağımlılıklarınızı istemci yerine işleyicinin etrafında tasarlayın. Daha da iyisi, bir işleyici enjekte etmenize izin veren HttpClient kullanan ve bunu enjekte edilebilir bağımlılığınız olarak kullanan soyut "hizmetler". Daha sonra testlerinizde, testlerinizi ayarlamak için yanıtı kontrol etmek için uygulayıcıyı taklit edebilirsiniz.

HttpClient'i paketlemek çılgınca bir zaman kaybıdır.

Güncelleme: Joshua Dooms örneğine bakın. Ben de tam olarak bunu tavsiye ediyorum.


17

Ayrıca yorum belirtildiği gibi yapmanız gerekir soyut uzakta HttpClientböylece o sonuca bağlı olmamak. Geçmişte benzer bir şey yaptım. Yapmaya çalıştığınız şeye benim yaptığım şeyi adapte etmeye çalışacağım.

Önce HttpClientsınıfa bakın ve bunun hangi işlevselliği sağladığına karar verin.

İşte bir olasılık:

public interface IHttpClient {
    System.Threading.Tasks.Task<T> DeleteAsync<T>(string uri) where T : class;
    System.Threading.Tasks.Task<T> DeleteAsync<T>(Uri uri) where T : class;
    System.Threading.Tasks.Task<T> GetAsync<T>(string uri) where T : class;
    System.Threading.Tasks.Task<T> GetAsync<T>(Uri uri) where T : class;
    System.Threading.Tasks.Task<T> PostAsync<T>(string uri, object package);
    System.Threading.Tasks.Task<T> PostAsync<T>(Uri uri, object package);
    System.Threading.Tasks.Task<T> PutAsync<T>(string uri, object package);
    System.Threading.Tasks.Task<T> PutAsync<T>(Uri uri, object package);
}

Yine daha önce belirtildiği gibi bu belirli amaçlar içindi. İlgili herhangi bir şeye olan bağımlılıkların çoğunu tamamen soyutladım HttpClientve iade etmek istediklerime odaklandım. HttpClientYalnızca istediğiniz gerekli işlevselliği sağlamak için nasıl soyutlamak istediğinizi değerlendirmelisiniz .

Bu, şimdi yalnızca test edilmesi gerekenlerle alay etmenize izin verecektir.

Hatta IHttpHandlertamamen ortadan kaldırmanızı ve HttpClientsoyutlamayı kullanmanızı bile tavsiye ederim IHttpClient. Ancak, işleyici arayüzünüzün gövdesini soyutlanmış istemcinin üyeleriyle değiştirebileceğiniz için seçim yapmıyorum.

Daha IHttpClientsonra gerçek / somut HttpClientveya başka herhangi bir nesneyi bu konu için sarmak / uyarlamak için bir uygulaması kullanılabilir; bu, gerçekten istediğiniz gibi HTTP istekleri yapmak için kullanılabilir, bu işlevselliği HttpClientözel olarak uygulandığı gibi sağlayan bir hizmettir . Soyutlamayı kullanmak temiz (Benim fikrim) ve SOLID bir yaklaşımdır ve çerçeve değiştikçe temeldeki istemciyi başka bir şey için değiştirmeniz gerekirse kodunuzu daha sürdürülebilir hale getirebilir.

İşte bir uygulamanın nasıl yapılabileceğine dair bir pasaj.

/// <summary>
/// HTTP Client adaptor wraps a <see cref="System.Net.Http.HttpClient"/> 
/// that contains a reference to <see cref="ConfigurableMessageHandler"/>
/// </summary>
public sealed class HttpClientAdaptor : IHttpClient {
    HttpClient httpClient;

    public HttpClientAdaptor(IHttpClientFactory httpClientFactory) {
        httpClient = httpClientFactory.CreateHttpClient(**Custom configurations**);
    }

    //...other code

     /// <summary>
    ///  Send a GET request to the specified Uri as an asynchronous operation.
    /// </summary>
    /// <typeparam name="T">Response type</typeparam>
    /// <param name="uri">The Uri the request is sent to</param>
    /// <returns></returns>
    public async System.Threading.Tasks.Task<T> GetAsync<T>(Uri uri) where T : class {
        var result = default(T);
        //Try to get content as T
        try {
            //send request and get the response
            var response = await httpClient.GetAsync(uri).ConfigureAwait(false);
            //if there is content in response to deserialize
            if (response.Content.Headers.ContentLength.GetValueOrDefault() > 0) {
                //get the content
                string responseBodyAsText = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
                //desrialize it
                result = deserializeJsonToObject<T>(responseBodyAsText);
            }
        } catch (Exception ex) {
            Log.Error(ex);
        }
        return result;
    }

    //...other code
}

Yukarıdaki örnekte görebileceğiniz gibi, genellikle kullanımla ilişkilendirilen birçok ağır kaldırma HttpClient, soyutlamanın arkasında gizlidir.

Bağlantı sınıfınız daha sonra soyutlanmış istemci ile enjekte edilebilir.

public class Connection
{
    private IHttpClient _httpClient;

    public Connection(IHttpClient httpClient)
    {
        _httpClient = httpClient;
    }
}

Testiniz daha sonra SUT'nuz için gerekli olanla dalga geçebilir

private IHttpClient _httpClient;

[TestMethod]
public void TestMockConnection()
{
    SomeModelObject model = new SomeModelObject();
    var httpClientMock = new Mock<IHttpClient>();
    httpClientMock.Setup(c => c.GetAsync<SomeModelObject>(It.IsAny<string>()))
        .Returns(() => Task.FromResult(model));

    _httpClient = httpClientMock.Object;

    var client = new Connection(_httpClient);

    // Assuming doSomething uses the client to make
    // a request for a model of type SomeModelObject
    client.doSomething();  
}

İşte cevap. Yukarıdaki bir soyutlama HttpClientve kullanarak özel örneğinizi oluşturmak için bir adaptör HttpClientFactory. Bunu yapmak, mantığın HTTP isteğinin ötesinde test edilmesini önemsiz hale getirir, buradaki amaç budur.
pimbrouwers

13

Diğer yanıtlara dayanarak, dışarıdan bağımlılığı olmayan bu kodu öneriyorum:

[TestClass]
public class MyTestClass
{
    [TestMethod]
    public async Task MyTestMethod()
    {
        var httpClient = new HttpClient(new MockHttpMessageHandler());

        var content = await httpClient.GetStringAsync("http://some.fake.url");

        Assert.AreEqual("Content as string", content);
    }
}

public class MockHttpMessageHandler : HttpMessageHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        var responseMessage = new HttpResponseMessage(HttpStatusCode.OK)
        {
            Content = new StringContent("Content as string")
        };

        return await Task.FromResult(responseMessage);
    }
}

4
Alayınızı etkili bir şekilde test ediyorsunuz. Bir taklidin gerçek gücü, beklentileri belirleyebilmeniz ve her testte davranışını değiştirebilmenizdir. Bazılarını HttpMessageHandlerkendiniz uygulamanız gerektiği gerçeği, bunu neredeyse imkansız hale getiriyor - ve yapmanız gerekiyor çünkü yöntemler öyle protected internal.
MarioDS

3
@MarioDS Bence önemli olan, HTTP yanıtını alay ederek kodun geri kalanını test edebilmenizdir. HttpClient'i alan bir fabrika enjekte ederseniz, testlerde bu HttpClient'i sağlayabilirsiniz.
chris31389

13

Sanırım mesele, onu biraz alt üst etmen.

public class AuroraClient : IAuroraClient
{
    private readonly HttpClient _client;

    public AuroraClient() : this(new HttpClientHandler())
    {
    }

    public AuroraClient(HttpMessageHandler messageHandler)
    {
        _client = new HttpClient(messageHandler);
    }
}

Yukarıdaki sınıfa bakarsanız, bence istediğiniz budur. Microsoft, en iyi performans için istemciyi canlı tutmanızı önerir, bu nedenle bu tür bir yapı bunu yapmanıza izin verir. Ayrıca HttpMessageHandler soyut bir sınıftır ve bu nedenle alay edilebilir. Test yönteminiz şu şekilde görünecektir:

[TestMethod]
public void TestMethod1()
{
    // Arrange
    var mockMessageHandler = new Mock<HttpMessageHandler>();
    // Set up your mock behavior here
    var auroraClient = new AuroraClient(mockMessageHandler.Object);
    // Act
    // Assert
}

Bu, HttpClient'ın davranışıyla alay ederken mantığınızı test etmenize olanak tanır.

Üzgünüm çocuklar, bunu yazıp kendim denedikten sonra, HttpMessageHandler'da korumalı yöntemlerle dalga geçemeyeceğinizi fark ettim. Daha sonra uygun bir taklitin enjeksiyonuna izin vermek için aşağıdaki kodu ekledim.

public interface IMockHttpMessageHandler
{
    Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);
}

public class MockHttpMessageHandler : HttpMessageHandler
{
    private readonly IMockHttpMessageHandler _realMockHandler;

    public MockHttpMessageHandler(IMockHttpMessageHandler realMockHandler)
    {
        _realMockHandler = realMockHandler;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        return await _realMockHandler.SendAsync(request, cancellationToken);
    }
}

Bununla yazılan testler aşağıdaki gibi görünür:

[TestMethod]
public async Task GetProductsReturnsDeserializedXmlXopData()
{
    // Arrange
    var mockMessageHandler = new Mock<IMockHttpMessageHandler>();
    // Set up Mock behavior here.
    var client = new AuroraClient(new MockHttpMessageHandler(mockMessageHandler.Object));
    // Act
    // Assert
}

9

Meslektaşlarımdan biri, çoğunun HttpClient yöntemlerinSendAsync(HttpRequestMessage request, CancellationToken cancellationToken) kaputun altında , bu da sanal bir yöntemdir HttpMessageInvoker:

Şimdiye kadar alay etmenin en kolay yolu HttpClient o belirli yöntemle dalga geçmekti:

var mockClient = new Mock<HttpClient>();
mockClient.Setup(client => client.SendAsync(It.IsAny<HttpRequestMessage>(), It.IsAny<CancellationToken>())).ReturnsAsync(_mockResponse.Object);

ve kodunuz çoğu (ancak hepsini değil) HttpClient normal yöntemler dahil olmak üzere sınıf yöntemlerinin

httpClient.SendAsync(req)

Https://github.com/dotnet/corefx/blob/master/src/System.Net.Http/src/System/Net/Http/HttpClient.cs'yi onaylamak için burayı kontrol edin


1
Bu, SendAsync(HttpRequestMessage)doğrudan çağıran herhangi bir kod için çalışmaz . Kodunuzu bu kullanışlılık işlevini kullanmayacak şekilde değiştirebilirseniz, o zaman HttpClient'i geçersiz kılarak doğrudan alay etmek SendAsyncaslında bulduğum en temiz çözümdür.
Dylan Nicholson

8

Bir alternatif, istek url'si ile eşleşen örüntüye göre hazır yanıtlar döndüren bir saplama HTTP sunucusu kurmak olabilir, yani gerçek HTTP isteklerini taklitleri değil test edersiniz. Tarihsel olarak bu önemli bir gelişme çabası gerektirirdi ve birim testi için dikkate alınması çok yavaş olurdu, ancak OSS kütüphanesi WireMock.net'in kullanımı kolaydır ve birçok testle çalıştırılabilecek kadar hızlıdır, bu nedenle dikkate değer olabilir. Kurulum birkaç satır koddur:

var server = FluentMockServer.Start();
server.Given(
      Request.Create()
      .WithPath("/some/thing").UsingGet()
   )
   .RespondWith(
       Response.Create()
       .WithStatusCode(200)
       .WithHeader("Content-Type", "application/json")
       .WithBody("{'attr':'value'}")
   );

Burada testlerde wiremock kullanımı hakkında daha fazla ayrıntı ve rehberlik bulabilirsiniz.


8

İşte benim için iyi çalışan basit bir çözüm.

Moq alay kitaplığını kullanma.

// ARRANGE
var handlerMock = new Mock<HttpMessageHandler>(MockBehavior.Strict);
handlerMock
   .Protected()
   // Setup the PROTECTED method to mock
   .Setup<Task<HttpResponseMessage>>(
      "SendAsync",
      ItExpr.IsAny<HttpRequestMessage>(),
      ItExpr.IsAny<CancellationToken>()
   )
   // prepare the expected response of the mocked http call
   .ReturnsAsync(new HttpResponseMessage()
   {
      StatusCode = HttpStatusCode.OK,
      Content = new StringContent("[{'id':1,'value':'1'}]"),
   })
   .Verifiable();

// use real http client with mocked handler here
var httpClient = new HttpClient(handlerMock.Object)
{
   BaseAddress = new Uri("http://test.com/"),
};

var subjectUnderTest = new MyTestClass(httpClient);

// ACT
var result = await subjectUnderTest
   .GetSomethingRemoteAsync('api/test/whatever');

// ASSERT
result.Should().NotBeNull(); // this is fluent assertions here...
result.Id.Should().Be(1);

// also check the 'http' call was like we expected it
var expectedUri = new Uri("http://test.com/api/test/whatever");

handlerMock.Protected().Verify(
   "SendAsync",
   Times.Exactly(1), // we expected a single external request
   ItExpr.Is<HttpRequestMessage>(req =>
      req.Method == HttpMethod.Get  // we expected a GET request
      && req.RequestUri == expectedUri // to this uri
   ),
   ItExpr.IsAny<CancellationToken>()
);

Kaynak: https://gingter.org/2018/07/26/how-to-mock-httpclient-in-your-net-c-unit-tests/


Bunu da başarılı bir şekilde kullandım. Bunu, daha fazla nuget bağımlılığı içinde dfragging yapmaya tercih ediyorum ve aslında kaputun altında neler olduğu hakkında biraz daha fazla şey öğreniyorsunuz. Güzel olan şey, yöntemlerin çoğunun SendAsynczaten kullanılmaya başlamasıdır, bu yüzden ekstra kurulum gerekmez.
Steve Pettifer

4

Cevapların çoğuna ikna olmadım.

Öncelikle, kullanan bir yöntemi birim test etmek istediğinizi hayal edin HttpClient. HttpClientDoğrudan uygulamanızda somutlaştırmamalısınız . Size bir örnek sağlama sorumluluğu olan bir fabrika enjekte HttpClientetmelisiniz. Bu şekilde, daha sonra o fabrikada alay edebilir ve hangisini HttpClientisterseniz geri dönebilirsiniz (örneğin: HttpClientgerçek değil, bir sahte ).

Yani, aşağıdaki gibi bir fabrikanız olur:

public interface IHttpClientFactory
{
    HttpClient Create();
}

Ve bir uygulama:

public class HttpClientFactory
    : IHttpClientFactory
{
    public HttpClient Create()
    {
        var httpClient = new HttpClient();
        return httpClient;
    }
}

Elbette bu uygulamaya IoC Container'a kaydolmanız gerekir. Autofac kullanırsanız, aşağıdaki gibi olur:

builder
    .RegisterType<IHttpClientFactory>()
    .As<HttpClientFactory>()
    .SingleInstance();

Artık düzgün ve test edilebilir bir uygulamaya sahip olacaksınız. Yönteminizin şöyle bir şey olduğunu hayal edin:

public class MyHttpClient
    : IMyHttpClient
{
    private readonly IHttpClientFactory _httpClientFactory;

    public SalesOrderHttpClient(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }

    public async Task<string> PostAsync(Uri uri, string content)
    {
        using (var client = _httpClientFactory.Create())
        {
            var clientAddress = uri.GetLeftPart(UriPartial.Authority);
            client.BaseAddress = new Uri(clientAddress);
            var content = new StringContent(content, Encoding.UTF8, "application/json");
            var uriAbsolutePath = uri.AbsolutePath;
            var response = await client.PostAsync(uriAbsolutePath, content);
            var responseJson = response.Content.ReadAsStringAsync().Result;
            return responseJson;
        }
    }
}

Şimdi test kısmı. soyut olan HttpClientgenişler HttpMessageHandler. HttpMessageHandlerTemsilciyi kabul eden bir "taklit" oluşturalım , böylece taklidi kullandığımızda her bir test için her davranışı ayarlayabiliriz.

public class MockHttpMessageHandler 
    : HttpMessageHandler
{
    private readonly Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> _sendAsyncFunc;

    public MockHttpMessageHandler(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> sendAsyncFunc)
    {
        _sendAsyncFunc = sendAsyncFunc;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        return await _sendAsyncFunc.Invoke(request, cancellationToken);
    }
}

Ve şimdi, Moq'un (ve birim testlerini daha okunaklı hale getiren bir kitaplık olan FluentAssertions'ın) yardımıyla, PostAsync yöntemimizi kullanarak birim testi yapmak için gereken her şeye sahibiz. HttpClient

public static class PostAsyncTests
{
    public class Given_A_Uri_And_A_JsonMessage_When_Posting_Async
        : Given_WhenAsync_Then_Test
    {
        private SalesOrderHttpClient _sut;
        private Uri _uri;
        private string _content;
        private string _expectedResult;
        private string _result;

        protected override void Given()
        {
            _uri = new Uri("http://test.com/api/resources");
            _content = "{\"foo\": \"bar\"}";
            _expectedResult = "{\"result\": \"ok\"}";

            var httpClientFactoryMock = new Mock<IHttpClientFactory>();
            var messageHandlerMock =
                new MockHttpMessageHandler((request, cancellation) =>
                {
                    var responseMessage =
                        new HttpResponseMessage(HttpStatusCode.Created)
                        {
                            Content = new StringContent("{\"result\": \"ok\"}")
                        };

                    var result = Task.FromResult(responseMessage);
                    return result;
                });

            var httpClient = new HttpClient(messageHandlerMock);
            httpClientFactoryMock
                .Setup(x => x.Create())
                .Returns(httpClient);

            var httpClientFactory = httpClientFactoryMock.Object;

            _sut = new SalesOrderHttpClient(httpClientFactory);
        }

        protected override async Task WhenAsync()
        {
            _result = await _sut.PostAsync(_uri, _content);
        }


        [Fact]
        public void Then_It_Should_Return_A_Valid_JsonMessage()
        {
            _result.Should().BeEquivalentTo(_expectedResult);
        }
    }
}

Açıkçası bu test aptalca ve biz gerçekten taklitimizi test ediyoruz. Ama fikri anladın. Uygulamanıza bağlı olarak anlamlı mantığı test etmelisiniz.

  • cevabın kod durumu 201 değilse, bir istisna oluşturmalı mı?
  • yanıt metni ayrıştırılamazsa ne olur?
  • vb.

Bu cevabın amacı, HttpClient kullanan bir şeyi test etmekti ve bu, bunu yapmanın güzel ve temiz bir yoludur.


4

Partiye biraz geç katıldım, ancak wiremocking kullanmayı seviyorum ( https://github.com/WireMock-Net/WireMock.Net mümkün olduğunca aşağı akış REST bağımlılıkları olan bir dotnet çekirdek mikro hizmetinin entegrasyon ) .

IHttpClientFactory'yi genişleten bir TestHttpClientFactory uygulayarak yöntemi geçersiz kılabiliriz

HttpClient CreateClient (dize adı)

Dolayısıyla, uygulamanızda adlandırılmış istemcileri kullanırken, kablo kilidinize kablolu bir HttpClient döndürme kontrolü sizdedir.

Bu yaklaşımla ilgili iyi olan şey, test ettiğiniz uygulama içinde hiçbir şeyi değiştirmemeniz ve hizmetinize gerçek bir REST isteği yaparak ve gerçek aşağı akış isteğinin geri dönmesi gereken json (veya her neyse) ile alay eden kurs entegrasyon testlerini etkinleştirmenizdir. Bu, kısa testlere ve başvurunuzda olabildiğince az alay etmeye yol açar.

    public class TestHttpClientFactory : IHttpClientFactory 
{
    public HttpClient CreateClient(string name)
    {
        var httpClient = new HttpClient
        {
            BaseAddress = new Uri(G.Config.Get<string>($"App:Endpoints:{name}"))
            // G.Config is our singleton config access, so the endpoint 
            // to the running wiremock is used in the test
        };
        return httpClient;
    }
}

ve

// in bootstrap of your Microservice
IHttpClientFactory factory = new TestHttpClientFactory();
container.Register<IHttpClientFactory>(factory);

2

Yana HttpClientkullanılması SendAsyncyöntemi tüm gerçekleştirmek için HTTP Requestsşunları yapabilirsiniz override SendAsyncyöntem ve alayHttpClient .

Aşağıdakine benzer HttpClientbir interfaceşey yaratan bu sargı için

public interface IServiceHelper
{
    HttpClient GetClient();
}

Ardından interface, hizmetinizde bağımlılık enjeksiyonu için yukarıda kullanın , aşağıdaki örnek

public class SampleService
{
    private readonly IServiceHelper serviceHelper;

    public SampleService(IServiceHelper serviceHelper)
    {
        this.serviceHelper = serviceHelper;
    }

    public async Task<HttpResponseMessage> Get(int dummyParam)
    {
        try
        {
            var dummyUrl = "http://www.dummyurl.com/api/controller/" + dummyParam;
            var client = serviceHelper.GetClient();
            HttpResponseMessage response = await client.GetAsync(dummyUrl);               

            return response;
        }
        catch (Exception)
        {
            // log.
            throw;
        }
    }
}

Şimdi birim test projesinde alay için bir yardımcı sınıf oluşturun SendAsync. Burada , yöntemi geçersiz kılmak için bir seçenek sağlayacak FakeHttpResponseHandlerolan bir sınıftır . Geçersiz kılma sonra kurulum için her biri için bir yanıt yöntemi ihtiyacını çağırıyor hangi bir oluşturmak için, yöntem ile olduğu gibi ve olduğu gibi yani orada bir olduğunda bu ve eğer maçlar dönecektir yapılandırılmış .inheriting DelegatingHandlerSendAsyncSendAsyncHTTP RequestSendAsyncDictionarykeyUrivalueHttpResponseMessageHTTP RequestUriSendAsyncHttpResponseMessage

public class FakeHttpResponseHandler : DelegatingHandler
{
    private readonly IDictionary<Uri, HttpResponseMessage> fakeServiceResponse;
    private readonly JavaScriptSerializer javaScriptSerializer;
    public FakeHttpResponseHandler()
    {
        fakeServiceResponse =  new Dictionary<Uri, HttpResponseMessage>();
        javaScriptSerializer =  new JavaScriptSerializer();
    }

    /// <summary>
    /// Used for adding fake httpResponseMessage for the httpClient operation.
    /// </summary>
    /// <typeparam name="TQueryStringParameter"> query string parameter </typeparam>
    /// <param name="uri">Service end point URL.</param>
    /// <param name="httpResponseMessage"> Response expected when the service called.</param>
    public void AddFakeServiceResponse(Uri uri, HttpResponseMessage httpResponseMessage)
    {
        fakeServiceResponse.Remove(uri);
        fakeServiceResponse.Add(uri, httpResponseMessage);
    }

    /// <summary>
    /// Used for adding fake httpResponseMessage for the httpClient operation having query string parameter.
    /// </summary>
    /// <typeparam name="TQueryStringParameter"> query string parameter </typeparam>
    /// <param name="uri">Service end point URL.</param>
    /// <param name="httpResponseMessage"> Response expected when the service called.</param>
    /// <param name="requestParameter">Query string parameter.</param>
    public void AddFakeServiceResponse<TQueryStringParameter>(Uri uri, HttpResponseMessage httpResponseMessage, TQueryStringParameter requestParameter)
    {
        var serilizedQueryStringParameter = javaScriptSerializer.Serialize(requestParameter);
        var actualUri = new Uri(string.Concat(uri, serilizedQueryStringParameter));
        fakeServiceResponse.Remove(actualUri);
        fakeServiceResponse.Add(actualUri, httpResponseMessage);
    }

    // all method in HttpClient call use SendAsync method internally so we are overriding that method here.
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        if(fakeServiceResponse.ContainsKey(request.RequestUri))
        {
            return Task.FromResult(fakeServiceResponse[request.RequestUri]);
        }

        return Task.FromResult(new HttpResponseMessage(HttpStatusCode.NotFound)
        {
            RequestMessage = request,
            Content = new StringContent("Not matching fake found")
        });
    }
}

IServiceHelperÇerçeveyi alay ederek veya aşağıdaki gibi yeni bir uygulama oluşturun . Bu FakeServiceHelpersınıfı, FakeHttpResponseHandlersınıfı enjekte etmek için kullanabiliriz, böylece bunun HttpClienttarafından yaratıldığında gerçek uygulama yerine classkullanacaktır FakeHttpResponseHandler class.

public class FakeServiceHelper : IServiceHelper
{
    private readonly DelegatingHandler delegatingHandler;

    public FakeServiceHelper(DelegatingHandler delegatingHandler)
    {
        this.delegatingHandler = delegatingHandler;
    }

    public HttpClient GetClient()
    {
        return new HttpClient(delegatingHandler);
    }
}

Ve Test yapılandırmak içinde FakeHttpResponseHandler classeklenerek Urive beklenen HttpResponseMessage. UriGerçek olmalı serviceson nokta Urizaman ki overridden SendAsyncyöntem, gerçek çağrılır serviceuygulanması o maç olacak Uriiçinde Dictionaryyapılandırılan ve cevap HttpResponseMessage. Yapılandırdıktan sonra FakeHttpResponseHandler objectsahte IServiceHelperuygulamaya enjekte edin . Ardından FakeServiceHelper class, asıl hizmetin override SendAsyncyöntemi kullanmasını sağlayacak gerçek hizmete enjekte edin .

[TestClass]
public class SampleServiceTest
{
    private FakeHttpResponseHandler fakeHttpResponseHandler;

    [TestInitialize]
    public void Initialize()
    {
        fakeHttpResponseHandler = new FakeHttpResponseHandler();
    }

    [TestMethod]
    public async Task GetMethodShouldReturnFakeResponse()
    {
        Uri uri = new Uri("http://www.dummyurl.com/api/controller/");
        const int dummyParam = 123456;
        const string expectdBody = "Expected Response";

        var expectedHttpResponseMessage = new HttpResponseMessage(HttpStatusCode.OK)
        {
            Content = new StringContent(expectdBody)
        };

        fakeHttpResponseHandler.AddFakeServiceResponse(uri, expectedHttpResponseMessage, dummyParam);

        var fakeServiceHelper = new FakeServiceHelper(fakeHttpResponseHandler);

        var sut = new SampleService(fakeServiceHelper);

        var response = await sut.Get(dummyParam);

        var responseBody = await response.Content.ReadAsStringAsync();

        Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
        Assert.AreEqual(expectdBody, responseBody);
    }
}

GitHub Bağlantısı: örnek uygulaması olan


Bu kod soruyu çözebilirken, sorunun nasıl ve neden çözüldüğüne dair bir açıklama da dahil olmak üzere , gönderinizin kalitesini artırmaya gerçekten yardımcı olur ve muhtemelen daha fazla oy almanıza neden olur. Sadece şu anda soran kişi için değil, gelecekte okuyucular için soruyu yanıtladığınızı unutmayın. Açıklamalar eklemek ve hangi sınırlamaların ve varsayımların geçerli olduğuna dair bir gösterge vermek için lütfen yanıtınızı düzenleyin .
Богдан Опир

Güncellenen geri bildirim açıklaması için @ БогданОпир teşekkür ederiz.
ghosh-arun

1

HttpMessageHandler ile alay eden RichardSzalay MockHttp kitaplığını kullanabilirsiniz ve testler sırasında kullanılacak bir HttpClient nesnesi döndürebilir.

GitHub MockHttp

PM> Kurulum Paketi RichardSzalay.MockHttp

GitHub belgelerinden

MockHttp, akıcı bir yapılandırma API'si sağlayan ve hazır yanıt sağlayan, HttpClient'i çalıştıran motor olan yedek bir HttpMessageHandler'ı tanımlar. Arayan (örneğin, uygulamanızın hizmet katmanı) varlığından habersiz kalır.

GitHub'dan örnek

 var mockHttp = new MockHttpMessageHandler();

// Setup a respond for the user api (including a wildcard in the URL)
mockHttp.When("http://localhost/api/user/*")
        .Respond("application/json", "{'name' : 'Test McGee'}"); // Respond with JSON

// Inject the handler or client into your application code
var client = mockHttp.ToHttpClient();

var response = await client.GetAsync("http://localhost/api/user/1234");
// or without async: var response = client.GetAsync("http://localhost/api/user/1234").Result;

var json = await response.Content.ReadAsStringAsync();

// No network connection required
Console.Write(json); // {'name' : 'Test McGee'}

1

Bu eski bir soru, ancak cevapları burada görmediğim bir çözümle genişletme dürtüsü hissediyorum.
Microsoft düzeneğini (System.Net.Http) taklit edebilir ve ardından test sırasında ShinsContext'i kullanabilirsiniz.

  1. VS 2017'de, System.Net.Http montajına sağ tıklayın ve "Sahte Montaj Ekle" yi seçin
  2. Kodunuzu kullanarak ShimsContext.Create () altındaki birim test yöntemine koyun. Bu şekilde, HttpClient'i taklit etmeyi planladığınız kodu izole edebilirsiniz.
  3. Uygulamanıza ve testinize bağlı olarak, istenen tüm eylemleri HttpClient üzerinde bir yöntem çağırdığınız ve döndürülen değeri taklit etmek istediğiniz yerde uygulamanızı öneririm. ShimHttpClient.AllInstances'ı kullanmak, testiniz sırasında oluşturulan tüm örneklerde uygulamanızı sahte hale getirir. Örneğin, GetAsync () yöntemini taklit etmek istiyorsanız, aşağıdakileri yapın:

    [TestMethod]
    public void FakeHttpClient()
    {
        using (ShimsContext.Create())
        {
            System.Net.Http.Fakes.ShimHttpClient.AllInstances.GetAsyncString = (c, requestUri) =>
            {
              //Return a service unavailable response
              var httpResponseMessage = new HttpResponseMessage(HttpStatusCode.ServiceUnavailable);
              var task = Task.FromResult(httpResponseMessage);
              return task;
            };
    
            //your implementation will use the fake method(s) automatically
            var client = new Connection(_httpClient);
            client.doSomething(); 
        }
    }

1

DI ortamında olduğu için çok basit bir şey yaptım.

public class HttpHelper : IHttpHelper
{
    private ILogHelper _logHelper;

    public HttpHelper(ILogHelper logHelper)
    {
        _logHelper = logHelper;
    }

    public virtual async Task<HttpResponseMessage> GetAsync(string uri, Dictionary<string, string> headers = null)
    {
        HttpResponseMessage response;
        using (var client = new HttpClient())
        {
            if (headers != null)
            {
                foreach (var h in headers)
                {
                    client.DefaultRequestHeaders.Add(h.Key, h.Value);
                }
            }
            response = await client.GetAsync(uri);
        }

        return response;
    }

    public async Task<T> GetAsync<T>(string uri, Dictionary<string, string> headers = null)
    {
        ...

        rawResponse = await GetAsync(uri, headers);

        ...
    }

}

ve alay:

    [TestInitialize]
    public void Initialize()
    {
       ...
        _httpHelper = new Mock<HttpHelper>(_logHelper.Object) { CallBase = true };
       ...
    }

    [TestMethod]
    public async Task SuccessStatusCode_WithAuthHeader()
    {
        ...

        _httpHelper.Setup(m => m.GetAsync(_uri, myHeaders)).Returns(
            Task<HttpResponseMessage>.Factory.StartNew(() =>
            {
                return new HttpResponseMessage(System.Net.HttpStatusCode.OK)
                {
                    Content = new StringContent(JsonConvert.SerializeObject(_testData))
                };
            })
        );
        var result = await _httpHelper.Object.GetAsync<TestDTO>(...);

        Assert.AreEqual(...);
    }

1

İhtiyacınız HttpMessageHandlerolan tek şey, HttpClientctor'a aktardığınız sınıfın test sürümüdür . Asıl nokta, test HttpMessageHandlersınıfınızın HttpRequestHandlerarayanların ayarlayabileceği ve HttpRequestistedikleri gibi basitçe idare edebileceği bir temsilciye sahip olmasıdır .

public class FakeHttpMessageHandler : HttpMessageHandler
    {
        public Func<HttpRequestMessage, CancellationToken, HttpResponseMessage> HttpRequestHandler { get; set; } =
        (r, c) => 
            new HttpResponseMessage
            {
                ReasonPhrase = r.RequestUri.AbsoluteUri,
                StatusCode = HttpStatusCode.OK
            };


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

Somut bir HttpClient örneği oluşturmak için bu sınıfın bir örneğini kullanabilirsiniz. HttpRequestHandler temsilcisi aracılığıyla, HttpClient'ten giden http istekleri üzerinde tam denetime sahip olursunuz.


1

PointZeroTwo'nun cevabından esinlenerek , işte NUnit ve FakeItEasy kullanan bir örnek .

SystemUnderTest bu örnekte test etmek istediğiniz sınıf - bunun için örnek içerik verilmemiştir, ancak zaten buna sahip olduğunuzu varsayıyorum!

[TestFixture]
public class HttpClientTests
{
    private ISystemUnderTest _systemUnderTest;
    private HttpMessageHandler _mockMessageHandler;

    [SetUp]
    public void Setup()
    {
        _mockMessageHandler = A.Fake<HttpMessageHandler>();
        var httpClient = new HttpClient(_mockMessageHandler);

        _systemUnderTest = new SystemUnderTest(httpClient);
    }

    [Test]
    public void HttpError()
    {
        // Arrange
        A.CallTo(_mockMessageHandler)
            .Where(x => x.Method.Name == "SendAsync")
            .WithReturnType<Task<HttpResponseMessage>>()
            .Returns(Task.FromResult(new HttpResponseMessage
            {
                StatusCode = HttpStatusCode.InternalServerError,
                Content = new StringContent("abcd")
            }));

        // Act
        var result = _systemUnderTest.DoSomething();

        // Assert
        // Assert.AreEqual(...);
    }
}

"x.Method.Name" ile belirtilen yönteme bir parametre geçirmek istersem ne olur ..?
Shailesh

0

Belki mevcut projenizde değiştirilecek bazı kodlar olabilir, ancak yeni projeler için kesinlikle Flurl kullanmayı düşünmelisiniz.

https://flurl.dev

Bu, HTTP istekleri yapmak için onu kullanan kod için özel olarak test edilebilirliği etkinleştiren akıcı bir arayüze sahip .NET için bir HTTP istemci kitaplığıdır.

Web sitesinde çok sayıda kod örneği var ama kısaca kodunuzda bu şekilde kullanıyorsunuz.

Kullanımları ekleyin.

using Flurl;
using Flurl.Http;

Bir alma isteği gönderin ve yanıtı okuyun.

public async Task SendGetRequest()
{
   var response = await "https://example.com".GetAsync();
   // ...
}

Birim testlerinde Flurl, istenildiği gibi davranacak şekilde yapılandırılabilen ve ayrıca yapılan aramaları doğrulayan bir sahte gibi davranır.

using (var httpTest = new HttpTest())
{
   // Arrange
   httpTest.RespondWith("OK", 200);

   // Act
   await sut.SendGetRequest();

   // Assert
   httpTest.ShouldHaveCalled("https://example.com")
      .WithVerb(HttpMethod.Get);
}

0

Dikkatlice araştırdıktan sonra, bunu başarmak için en iyi yaklaşımı buldum.

    private HttpResponseMessage response;

    [SetUp]
    public void Setup()
    {
        var handlerMock = new Mock<HttpMessageHandler>();

        handlerMock
           .Protected()
           .Setup<Task<HttpResponseMessage>>(
              "SendAsync",
              ItExpr.IsAny<HttpRequestMessage>(),
              ItExpr.IsAny<CancellationToken>())
           // This line will let you to change the response in each test method
           .ReturnsAsync(() => response);

        _httpClient = new HttpClient(handlerMock.Object);

        yourClinet = new YourClient( _httpClient);
    }

Fark ettiğiniz gibi Moq ve Moq.Protected paketleri kullandım.


0

2 sentimi eklemek için. Belirli http istek yöntemleriyle alay etmek için Al veya Gönder. Bu benim için çalıştı.

mockHttpMessageHandler.Protected().Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.Is<HttpRequestMessage>(a => a.Method == HttpMethod.Get), ItExpr.IsAny<CancellationToken>())
                                                .Returns(Task.FromResult(new HttpResponseMessage()
                                                {
                                                    StatusCode = HttpStatusCode.OK,
                                                    Content = new StringContent(""),
                                                })).Verifiable();
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.