Saya tidak yakin dengan banyak jawaban.
Pertama-tama, bayangkan Anda ingin menguji unit metode yang digunakan HttpClient
. Anda tidak boleh membuat instance HttpClient
langsung dalam implementasi Anda. Anda harus menyuntikkan pabrik dengan tanggung jawab menyediakan contoh HttpClient
untuk Anda. Dengan cara itu Anda dapat mengejek nanti di pabrik itu dan mengembalikan mana pun yang HttpClient
Anda inginkan (misalnya: tiruan HttpClient
dan 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. HttpClient
meluas HttpMessageHandler
, yang abstrak. Mari kita buat "tiruan" HttpMessageHandler
yang 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.
HttpClient
di antarmuka Anda adalah tempat masalahnya. Anda memaksa klien Anda untuk menggunakanHttpClient
kelas beton. Sebaliknya, Anda harus mengekspos abstraksi fileHttpClient
.