Mengolok-olok HttpClient dalam pengujian unit


111

Saya memiliki beberapa masalah saat mencoba membungkus kode saya untuk digunakan dalam pengujian unit. Masalahnya adalah ini. Saya Memiliki antarmuka IHttpHandler:

public interface IHttpHandler
{
    HttpClient client { get; }
}

Dan kelas yang menggunakannya, HttpHandler:

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

Dan kemudian kelas Connection, yang menggunakan simpleIOC untuk memasukkan implementasi klien:

public class Connection
{
    private IHttpHandler _httpClient;

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

Dan kemudian saya memiliki proyek pengujian unit yang memiliki kelas ini:

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?     

}

Sekarang jelas saya akan memiliki metode di kelas Connection yang akan mengambil data (JSON) dari back end saya. Namun, saya ingin menulis tes unit untuk kelas ini, dan jelas saya tidak ingin menulis tes terhadap back end yang sebenarnya, melainkan yang diejek. Saya telah mencoba google jawaban yang bagus untuk ini tanpa hasil yang besar. Saya dapat dan telah menggunakan Moq untuk mengejek sebelumnya, tetapi tidak pernah pada sesuatu seperti httpClient. Bagaimana saya harus mendekati masalah ini?

Terima kasih sebelumnya.


1
Mengekspos a HttpClientdi antarmuka Anda adalah tempat masalahnya. Anda memaksa klien Anda untuk menggunakan HttpClientkelas beton. Sebaliknya, Anda harus mengekspos abstraksi file HttpClient.
Mike Eason

Bisakah Anda menjelaskannya sedikit lebih dalam? Bagaimana saya harus membangun konstruktor kelas koneksi karena saya tidak ingin ketergantungan HttpClient di kelas lain menggunakan kelas Connection. Misalnya saya tidak ingin melewatkan HttpClient concerete di konstruktor Connection karena itu akan membuat setiap kelas lain yang menggunakan Connection tergantung dari HttpClient?
tjugg

Karena tertarik, apa yang Anda google? Rupanya mockhttp dapat menggunakan beberapa peningkatan SEO.
Richard Szalay

@ Mike - seperti yang disebutkan dalam jawaban saya, sebenarnya tidak perlu mengabstraksi HttpClient. Ini dapat diuji apa adanya. Saya memiliki banyak proyek yang memiliki rangkaian pengujian tanpa backend menggunakan metode ini.
Richard Szalay

Jawaban:


37

Antarmuka Anda mengekspos HttpClientkelas konkret , oleh karena itu setiap kelas yang menggunakan antarmuka ini terikat padanya, ini berarti kelas tersebut tidak dapat diejek.

HttpClienttidak mewarisi dari antarmuka mana pun, jadi Anda harus menulisnya sendiri. Saya menyarankan pola seperti dekorator :

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);
}

Dan kelas Anda akan terlihat seperti ini:

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);
    }
}

Inti dari semua ini adalah HttpClientHandlermembuat sendiri HttpClient, tentu saja Anda dapat membuat beberapa kelas yang diimplementasikan IHttpHandlerdengan cara yang berbeda.

Masalah utama dengan pendekatan ini adalah bahwa Anda secara efektif menulis kelas yang hanya memanggil metode di kelas lain, namun Anda dapat membuat kelas yang mewarisi dari HttpClient(Lihat contoh Nkosi , ini adalah pendekatan yang jauh lebih baik daripada milik saya). Hidup akan jauh lebih mudah jika HttpClientmemiliki antarmuka yang dapat Anda tiru, sayangnya tidak.

Namun, contoh ini bukanlah tiket emas. IHttpHandlermasih mengandalkan HttpResponseMessage, yang termasuk dalam System.Net.Httpnamespace, oleh karena itu jika Anda memerlukan implementasi lain selain HttpClient, Anda harus melakukan beberapa jenis pemetaan untuk mengubah tanggapan mereka menjadi HttpResponseMessageobjek. Hal ini tentu saja hanya masalah jika Anda perlu menggunakan beberapa implementasi dari IHttpHandlertetapi tidak terlihat seperti Anda melakukannya itu bukan akhir dari dunia, tapi sesuatu untuk dipikirkan.

Bagaimanapun, Anda dapat dengan mudah mengejek IHttpHandlertanpa harus khawatir tentang HttpClientkelas konkret karena telah disarikan.

Saya merekomendasikan pengujian metode non-asinkron , karena ini masih memanggil metode asinkron tetapi tanpa kerumitan harus khawatir tentang pengujian unit metode asinkron, lihat di sini


Ini memang menjawab pertanyaan saya. Jawaban Nkosis juga benar jadi saya tidak yakin mana yang harus saya terima sebagai jawaban, tetapi saya akan memilih yang ini. Terima kasih atas usahanya
tjugg

@tjugg Senang bisa membantu. Jangan ragu untuk memilih jawaban jika menurut Anda bermanfaat.
Nkosi

3
Perlu dicatat bahwa perbedaan utama antara jawaban ini dan Nkosi adalah abstraksi yang jauh lebih tipis. Tipis mungkin bagus untuk benda sederhana
Ben Aaronson

228

Ekstensibilitas HttpClient terletak pada HttpMessageHandlerditeruskan ke konstruktor. Maksudnya adalah untuk mengizinkan implementasi khusus platform, tetapi Anda juga dapat memalsukannya. Tidak perlu membuat pembungkus dekorator untuk HttpClient.

Jika Anda lebih suka DSL daripada menggunakan Moq, saya memiliki perpustakaan di GitHub / Nuget yang membuat segalanya menjadi lebih mudah: 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
Jadi saya hanya akan meneruskan MockHttpMessageHandler sebagai kelas Httphandler messagehandler? Atau bagaimana Anda menerapkannya dalam proyek Anda sendiri
tjugg

2
Jawaban bagus dan sesuatu yang awalnya tidak saya ketahui. Membuat bekerja dengan HttpClient tidak terlalu buruk.
Bealer

6
Untuk orang-orang yang tidak ingin berurusan dengan menyuntikkan klien tetapi masih ingin mudah diuji, itu hal yang sepele untuk dicapai. Cukup ganti var client = new HttpClient()dengan var client = ClientFactory()dan siapkan bidang internal static Func<HttpClient> ClientFactory = () => new HttpClient();dan pada tingkat pengujian Anda dapat menulis ulang bidang ini.
Chris Marisic

3
@ChrisMarisic Anda menyarankan bentuk lokasi layanan untuk menggantikan injeksi. Lokasi layanan adalah anti-pola yang terkenal sehingga injeksi lebih disukai.
MarioDS

2
@MarioDS dan apa pun itu, Anda tidak boleh memasukkan instance HttpClient sama sekali. Jika Anda sudah mati-matian menggunakan injeksi konstruktor untuk ini maka Anda harus menyuntikkan HttpClientFactoryseperti dalam Func<HttpClient>. Mengingat saya melihat HttpClient murni sebagai detail implementasi dan bukan ketergantungan, saya akan menggunakan statika seperti yang saya ilustrasikan di atas. Saya sepenuhnya baik-baik saja dengan tes yang memanipulasi internal. Jika saya peduli dengan pure-ism, saya akan berdiri di server penuh dan menguji jalur kode langsung. Menggunakan tiruan apa pun berarti Anda menerima perkiraan perilaku, bukan perilaku sebenarnya.
Chris Marisic

39

Saya setuju dengan beberapa jawaban lain bahwa pendekatan terbaik adalah mengejek HttpMessageHandler daripada membungkus HttpClient. Jawaban ini unik karena masih menyuntikkan HttpClient, memungkinkannya menjadi tunggal atau dikelola dengan injeksi ketergantungan.

"HttpClient dimaksudkan untuk dipakai sekali dan digunakan kembali sepanjang masa pakai aplikasi." ( Sumber ).

Mengolok-olok HttpMessageHandler bisa sedikit rumit karena SendAsync dilindungi. Berikut contoh lengkapnya, menggunakan xunit dan Moq.

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
Saya sangat menyukai ini. The mockMessageHandler.Protected()adalah si pembunuh. Terima kasih untuk contoh ini. Ini memungkinkan untuk menulis tes tanpa mengubah sumber sama sekali.
tyrion

1
FYI, Moq 4.8 mendukung ejekan yang sangat diketik terhadap anggota yang dilindungi - github.com/Moq/moq4/wiki/Quickstart
Richard Szalay

2
Ini terlihat bagus. Moq juga mendukung ReturnsAsync sehingga kodenya akan terlihat seperti.ReturnsAsync(new HttpResponseMessage {StatusCode = HttpStatusCode.OK, Content = new StringContent(testContent)})
kord

Terima kasih @kord, saya menambahkannya ke jawabannya
PointZeroTwo

3
Apakah ada cara untuk memverifikasi bahwa "SandAsync" dipanggil dengan beberapa parameter? Saya sudah mencoba menggunakan ... Protected (). Verify (...), tapi sepertinya itu tidak bekerja dengan metode async.
Rroman

29

Ini adalah pertanyaan umum, dan saya sangat menginginkan kemampuan untuk mengejek HttpClient, tetapi saya rasa akhirnya saya menyadari bahwa Anda tidak boleh mengejek HttpClient. Tampaknya logis untuk melakukannya, tetapi saya pikir kami telah dicuci otak oleh hal-hal yang kami lihat di perpustakaan sumber terbuka.

Kami sering melihat "Klien" di luar sana yang kami tiru dalam kode kami sehingga kami dapat menguji secara terpisah, jadi kami secara otomatis mencoba menerapkan prinsip yang sama ke HttpClient. HttpClient sebenarnya melakukan banyak hal; Anda dapat menganggapnya sebagai manajer untuk HttpMessageHandler, jadi Anda tidak ingin mengejeknya, dan itulah mengapa masih belum memiliki antarmuka. Bagian yang benar-benar Anda minati untuk pengujian unit, atau bahkan mendesain layanan Anda, adalah HttpMessageHandler karena itulah yang mengembalikan respons, dan Anda dapat mengejeknya.

Juga perlu diperhatikan bahwa Anda mungkin harus mulai memperlakukan HttpClient seperti kesepakatan yang lebih besar. Misalnya: Jaga instatiating Anda dari HttpClients baru seminimal mungkin. Gunakan kembali, mereka dirancang untuk digunakan kembali dan menggunakan sumber daya yang lebih sedikit jika Anda melakukannya. Jika Anda mulai memperlakukannya seperti kesepakatan yang lebih besar, akan terasa lebih salah jika ingin mengejeknya dan sekarang penangan pesan akan mulai menjadi hal yang Anda masukkan, bukan klien.

Dengan kata lain, rancang dependensi Anda di sekitar handler, bukan di klien. Bahkan lebih baik, "layanan" abstrak yang menggunakan HttpClient yang memungkinkan Anda memasukkan penangan, dan menggunakannya sebagai dependensi yang dapat diinjeksi. Kemudian dalam pengujian Anda, Anda dapat memalsukan penangan untuk mengontrol respons untuk menyiapkan pengujian Anda.

Membungkus HttpClient adalah pemborosan waktu yang gila-gilaan.

Pembaruan: Lihat contoh Joshua Dooms. Itu persis seperti yang saya rekomendasikan.


17

Seperti juga disebutkan dalam komentar Anda perlu abstrak pergi HttpClientagar tidak digabungkan untuk itu. Saya telah melakukan hal serupa di masa lalu. Saya akan mencoba menyesuaikan apa yang saya lakukan dengan apa yang Anda coba lakukan.

Pertama, lihat HttpClientkelas dan putuskan fungsionalitas apa yang disediakan yang akan dibutuhkan.

Berikut ini kemungkinannya:

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);
}

Sekali lagi, seperti yang dinyatakan sebelumnya, ini untuk tujuan tertentu. Saya benar-benar mengabstraksi sebagian besar ketergantungan pada apa pun yang berhubungan dengan HttpClientdan berfokus pada apa yang saya inginkan kembali. Anda harus mengevaluasi bagaimana Anda ingin mengabstraksi HttpClientuntuk menyediakan hanya fungsionalitas yang diperlukan yang Anda inginkan.

Ini sekarang akan memungkinkan Anda untuk hanya mengejek apa yang perlu diuji.

Saya bahkan akan merekomendasikan untuk menghapus IHttpHandlersepenuhnya dan menggunakan HttpClientabstraksi IHttpClient. Tetapi saya tidak memilih karena Anda dapat mengganti badan antarmuka penangan Anda dengan anggota klien yang diabstraksi.

Implementasi dari IHttpClientkemudian dapat digunakan untuk membungkus / mengadaptasi nyata / konkret HttpClientatau objek lain dalam hal ini, yang dapat digunakan untuk membuat permintaan HTTP karena apa yang Anda inginkan adalah layanan yang menyediakan fungsionalitas itu sebagaimana yang ditentukan HttpClientsecara khusus. Menggunakan abstraksi adalah pendekatan yang bersih (Pendapat saya) dan SOLID dan dapat membuat kode Anda lebih mudah dipelihara jika Anda perlu mengganti klien yang mendasarinya untuk sesuatu yang lain saat kerangka kerja berubah.

Berikut ini cuplikan bagaimana penerapan dapat dilakukan.

/// <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
}

Seperti yang Anda lihat pada contoh di atas, banyak pekerjaan berat yang biasanya terkait dengan penggunaan HttpClienttersembunyi di balik abstraksi.

Kelas koneksi Anda kemudian dapat diinjeksi dengan klien yang diabstraksi

public class Connection
{
    private IHttpClient _httpClient;

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

Tes Anda kemudian dapat mengejek apa yang dibutuhkan untuk SUT Anda

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();  
}

INILAH jawabannya. Abstraksi di atas HttpClientdan adaptor untuk membuat instance spesifik Anda menggunakan HttpClientFactory. Melakukan hal ini membuat pengujian logika di luar permintaan HTTP menjadi sepele, yang merupakan tujuannya di sini.
pimbrouwers

13

Berdasarkan jawaban lain, saya menyarankan kode ini, yang tidak memiliki ketergantungan di luar:

[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
Anda sedang menguji tiruan Anda secara efektif. Kekuatan sebenarnya dari tiruan adalah Anda dapat menetapkan ekspektasi dan mengubah perilakunya di setiap ujian. Fakta bahwa Anda harus menerapkan beberapa HttpMessageHandlersendiri membuat itu hampir tidak mungkin - dan Anda harus melakukannya karena metodenya protected internal.
MarioDS

3
@MarioDS Saya pikir intinya adalah Anda dapat mengejek respons HTTP sehingga Anda dapat menguji kode lainnya. Jika Anda menginjeksi pabrik yang mendapatkan HttpClient, maka dalam pengujian Anda dapat menyediakan HttpClient ini.
chris31389

13

Saya pikir masalahnya adalah Anda mendapatkannya sedikit terbalik.

public class AuroraClient : IAuroraClient
{
    private readonly HttpClient _client;

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

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

Jika Anda melihat kelas di atas, saya rasa inilah yang Anda inginkan. Microsoft menganjurkan agar klien tetap hidup untuk kinerja yang optimal, sehingga jenis struktur ini memungkinkan Anda melakukannya. Juga HttpMessageHandler adalah kelas abstrak dan oleh karena itu dapat diolok-olok. Metode pengujian Anda akan terlihat seperti ini:

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

Ini memungkinkan Anda untuk menguji logika Anda sambil mengejek perilaku HttpClient.

Maaf teman-teman, setelah menulis ini dan mencobanya sendiri, saya menyadari bahwa Anda tidak dapat mengejek metode yang dilindungi di HttpMessageHandler. Saya kemudian menambahkan kode berikut untuk memungkinkan injeksi tiruan yang tepat.

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);
    }
}

Tes yang ditulis dengan ini kemudian terlihat seperti berikut:

[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

Salah satu kolega saya memperhatikan bahwa sebagian besar HttpClientmetode semuanya memanggil di SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)bawah tenda, yang merupakan metode virtual dari HttpMessageInvoker:

Sejauh ini, cara termudah untuk mengejek HttpClientadalah dengan mengejek metode tertentu itu:

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

dan kode Anda bisa memanggil sebagian besar (tapi tidak semua) HttpClientmetode kelas, termasuk regular

httpClient.SendAsync(req)

Periksa di sini untuk mengonfirmasi https://github.com/dotnet/corefx/blob/master/src/System.Net.Http/src/System/Net/Http/HttpClient.cs


1
Ini tidak berfungsi untuk kode apa pun yang memanggil SendAsync(HttpRequestMessage)secara langsung. Jika Anda dapat mengubah kode untuk tidak menggunakan fungsi kemudahan ini, maka mengejek HttpClient secara langsung dengan menimpa SendAsyncsebenarnya adalah solusi terbersih yang pernah saya temukan.
Dylan Nicholson

8

Salah satu alternatifnya adalah menyiapkan server HTTP rintisan yang mengembalikan respons terekam berdasarkan pola yang cocok dengan url permintaan, yang berarti Anda menguji permintaan HTTP nyata, bukan mengejek. Secara historis, ini akan membutuhkan upaya pengembangan yang signifikan dan akan sangat lambat untuk dipertimbangkan untuk pengujian unit, namun pustaka OSS WireMock.net mudah digunakan dan cukup cepat untuk dijalankan dengan banyak pengujian sehingga mungkin perlu dipertimbangkan. Penyiapannya adalah beberapa baris kode:

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'}")
   );

Anda dapat menemukan detail dan panduan lebih lanjut tentang penggunaan wiremock dalam pengujian di sini.


8

Inilah solusi sederhana, yang bekerja dengan baik untuk saya.

Menggunakan pustaka moq mocking.

// 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>()
);

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


Saya juga telah menggunakan ini dengan sukses. Saya lebih suka ini daripada meringkuk dalam ketergantungan nuget yang lebih baik dan Anda benar-benar belajar lebih banyak tentang apa yang terjadi di balik terpal juga. Untung sebagian besar metode akhirnya digunakan SendAsyncjadi tidak diperlukan pengaturan tambahan.
Steve Pettifer

4

Saya tidak yakin dengan banyak jawaban.

Pertama-tama, bayangkan Anda ingin menguji unit metode yang digunakan HttpClient. Anda tidak boleh membuat instance HttpClientlangsung dalam implementasi Anda. Anda harus menyuntikkan pabrik dengan tanggung jawab menyediakan contoh HttpClientuntuk Anda. Dengan cara itu Anda dapat mengejek nanti di pabrik itu dan mengembalikan mana pun yang HttpClientAnda inginkan (misalnya: tiruan HttpClientdan bukan yang asli).

Jadi, Anda akan memiliki pabrik seperti berikut:

public interface IHttpClientFactory
{
    HttpClient Create();
}

Dan implementasinya:

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

Tentu saja Anda perlu mendaftar di Kontainer IoC Anda untuk implementasi ini. Jika Anda menggunakan Autofac, itu akan menjadi seperti ini:

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

Sekarang Anda akan memiliki implementasi yang tepat dan dapat diuji. Bayangkan metode Anda seperti ini:

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;
        }
    }
}

Sekarang bagian pengujian. HttpClientmeluas HttpMessageHandler, yang abstrak. Mari kita buat "tiruan" HttpMessageHandleryang menerima delegasi sehingga saat kita menggunakan tiruan kita juga bisa mengatur setiap perilaku untuk setiap pengujian.

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);
    }
}

Dan sekarang, dan dengan bantuan Moq (dan FluentAssertions, pustaka yang membuat pengujian unit lebih mudah dibaca), kami memiliki semua yang diperlukan untuk menguji unit metode kami PostAsync yang menggunakan 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);
        }
    }
}

Jelas tes ini konyol, dan kami benar-benar menguji tiruan kami. Tapi Anda mengerti. Anda harus menguji logika yang bermakna tergantung pada penerapan Anda seperti ..

  • jika status kode respon bukan 201, haruskah itu memunculkan pengecualian?
  • jika teks tanggapan tidak dapat diuraikan, apa yang harus terjadi?
  • dll.

Tujuan dari jawaban ini adalah untuk menguji sesuatu yang menggunakan HttpClient dan ini adalah cara bersih yang bagus untuk melakukannya.


4

Bergabung dengan pesta agak terlambat, tetapi saya suka menggunakan wiremocking ( https://github.com/WireMock-Net/WireMock.Net ) bila memungkinkan dalam uji integrasi layanan mikro inti dotnet dengan dependensi REST hilir.

Dengan mengimplementasikan TestHttpClientFactory yang memperluas IHttpClientFactory kita bisa mengganti metode

HttpClient CreateClient (nama string)

Jadi, saat menggunakan klien bernama dalam aplikasi Anda, Anda memegang kendali untuk mengembalikan HttpClient yang terhubung ke wiremock Anda.

Hal yang baik tentang pendekatan ini adalah Anda tidak mengubah apa pun dalam aplikasi yang Anda uji, dan memungkinkan pengujian integrasi kursus melakukan permintaan REST aktual ke layanan Anda dan mengejek json (atau apa pun) yang harus dikembalikan oleh permintaan downstream aktual. Hal ini mengarah pada pengujian yang ringkas dan sesedikit mungkin ejekan dalam aplikasi Anda.

    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;
    }
}

dan

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

2

Karena HttpClientmenggunakan SendAsyncmetode untuk melakukan semua HTTP Requests, Anda dapat override SendAsyncmenggunakan metode dan mengejek file HttpClient.

Untuk bungkus itu membuat HttpClientmenjadi interface, seperti di bawah ini

public interface IServiceHelper
{
    HttpClient GetClient();
}

Kemudian gunakan di atas interfaceuntuk injeksi dependensi dalam layanan Anda, contoh di bawah

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;
        }
    }
}

Sekarang dalam proyek uji unit buat kelas pembantu untuk mengejek SendAsync. Ini dia FakeHttpResponseHandlerkelas inheriting DelegatingHandleryang akan memberikan opsi untuk mengganti SendAsyncmetode. Setelah mengganti SendAsyncmetode perlu menyiapkan respons untuk masing-masing HTTP Requestyang memanggil SendAsyncmetode, untuk itu buat Dictionarydengan keyas Uridan valueas HttpResponseMessagesehingga setiap kali ada HTTP Requestdan jika Uripertandingan SendAsyncakan mengembalikan konfigurasi HttpResponseMessage.

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")
        });
    }
}

Buat implementasi baru untuk IServiceHelperdengan kerangka kerja tiruan atau seperti di bawah ini. FakeServiceHelperKelas ini dapat kita gunakan untuk menginjeksi FakeHttpResponseHandlerkelas sehingga setiap kali HttpClientdibuat oleh ini classakan digunakan FakeHttpResponseHandler classsebagai pengganti implementasi yang sebenarnya.

public class FakeServiceHelper : IServiceHelper
{
    private readonly DelegatingHandler delegatingHandler;

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

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

Dan di konfigurasi uji FakeHttpResponseHandler classdengan menambahkan Uridan diharapkan HttpResponseMessage. Ini Uriharus menjadi servicetitik akhir yang sebenarnya Urisehingga ketika overridden SendAsyncmetode dipanggil dari serviceimplementasi yang sebenarnya, ia akan cocok dengan Uriin Dictionarydan merespons dengan yang dikonfigurasi HttpResponseMessage. Setelah mengonfigurasi, injeksi FakeHttpResponseHandler objectke IServiceHelperimplementasi palsu . Kemudian masukkan FakeServiceHelper classke layanan aktual yang akan membuat layanan aktual menggunakan override SendAsyncmetode tersebut.

[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 Link: yang memiliki contoh implementasi


Meskipun kode ini dapat menyelesaikan pertanyaan, termasuk penjelasan tentang bagaimana dan mengapa ini menyelesaikan masalah akan sangat membantu untuk meningkatkan kualitas posting Anda, dan mungkin menghasilkan lebih banyak suara. Ingatlah bahwa Anda menjawab pertanyaan untuk pembaca di masa depan, bukan hanya orang yang bertanya sekarang. Harap edit jawaban Anda untuk menambahkan penjelasan dan memberikan indikasi batasan dan asumsi apa yang berlaku.
Богдан Опир

Terima kasih @ БогданОпир atas umpan balik penjelasan yang diperbarui.
ghosh-arun

1

Anda bisa menggunakan pustaka RichardSzalay MockHttp yang mengolok-olok HttpMessageHandler dan dapat mengembalikan objek HttpClient untuk digunakan selama pengujian.

GitHub MockHttp

PM> Instal-Paket RichardSzalay.MockHttp

Dari dokumentasi GitHub

MockHttp mendefinisikan HttpMessageHandler pengganti, mesin yang menggerakkan HttpClient, yang menyediakan API konfigurasi yang lancar dan menyediakan respons terekam. Pemanggil (misalnya lapisan layanan aplikasi Anda) tetap tidak menyadari keberadaannya.

Contoh dari GitHub

 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

Ini adalah pertanyaan lama, tetapi saya merasakan dorongan untuk memberikan jawaban dengan solusi yang tidak saya lihat di sini.
Anda dapat memalsukan Microsoft assemly (System.Net.Http) dan kemudian menggunakan ShinsContext selama pengujian.

  1. Di VS 2017, klik kanan pada perakitan System.Net.Http dan pilih "Add Fakes Assembly"
  2. Letakkan kode Anda dalam metode pengujian unit di bawah ShimsContext.Create () menggunakan. Dengan cara ini, Anda dapat mengisolasi kode di mana Anda berencana untuk memalsukan HttpClient.
  3. Bergantung pada implementasi dan pengujian Anda, saya akan menyarankan untuk menerapkan semua tindakan yang diinginkan di mana Anda memanggil metode pada HttpClient dan ingin memalsukan nilai yang dikembalikan. Menggunakan ShimHttpClient.AllInstances akan memalsukan implementasi Anda di semua instance yang dibuat selama pengujian. Misalnya, jika Anda ingin memalsukan metode GetAsync (), lakukan hal berikut:

    [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

Saya melakukan sesuatu yang sangat sederhana, karena saya berada di lingkungan DI.

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);

        ...
    }

}

dan tiruannya adalah:

    [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

Yang Anda butuhkan hanyalah versi uji HttpMessageHandlerkelas yang Anda teruskan ke HttpClientctor. Poin utamanya adalah bahwa HttpMessageHandlerkelas pengujian Anda akan memiliki HttpRequestHandlerdelegasi yang dapat diatur oleh pemanggil dan hanya menangani HttpRequestseperti yang mereka inginkan.

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));
        }
    }

Anda dapat menggunakan instance kelas ini untuk membuat instance HttpClient yang konkret. Melalui delegasi HttpRequestHandler Anda memiliki kendali penuh atas permintaan http keluar dari HttpClient.


1

Terinspirasi oleh jawaban PointZeroTwo , berikut ini contoh menggunakan NUnit dan FakeItEasy .

SystemUnderTest dalam contoh ini adalah kelas yang ingin Anda uji - tidak ada konten sampel yang diberikan untuk itu, tetapi saya berasumsi bahwa Anda sudah memilikinya!

[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(...);
    }
}

bagaimana jika saya ingin mengirimkan parameter ke metode, yang disebutkan di atas "x.Method.Name" ..?
Shailesh

0

Mungkin akan ada beberapa kode untuk diubah dalam proyek Anda saat ini, tetapi untuk proyek baru Anda harus benar-benar mempertimbangkan untuk menggunakan Flurl.

https://flurl.dev

Ini adalah pustaka klien HTTP untuk .NET dengan antarmuka fasih yang secara khusus memungkinkan pengujian untuk kode yang menggunakannya untuk membuat permintaan HTTP.

Ada banyak contoh kode di situs web tetapi singkatnya Anda menggunakannya seperti ini di kode Anda.

Tambahkan penggunaan.

using Flurl;
using Flurl.Http;

Kirim permintaan get dan baca tanggapannya.

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

Dalam pengujian unit Flurl bertindak sebagai tiruan yang dapat dikonfigurasi untuk berperilaku seperti yang diinginkan dan juga untuk memverifikasi panggilan yang dilakukan.

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

   // Act
   await sut.SendGetRequest();

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

0

Setelah mencari dengan cermat, saya menemukan pendekatan terbaik untuk mencapai ini.

    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);
    }

Seperti yang Anda perhatikan, saya telah menggunakan paket Moq dan Moq. Yang dilindungi.


0

Untuk menambahkan 2 sen saya. Untuk meniru metode permintaan http tertentu, baik Dapatkan atau Posting. Ini berhasil untuk saya.

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();
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.