ฉันไม่มั่นใจในหลาย ๆ คำตอบ
HttpClient
ครั้งแรกของทั้งหมดคิดว่าคุณต้องการที่จะใช้วิธีการทดสอบหน่วยที่ใช้ คุณไม่ควรสร้างอินสแตนซ์HttpClient
โดยตรงในการใช้งานของคุณ คุณควรฉีดโรงงานด้วยความรับผิดชอบในการจัดหาตัวอย่างHttpClient
ให้คุณ ด้วยวิธีนี้คุณสามารถเยาะเย้ยโรงงานนั้นในภายหลังและส่งคืนตามที่HttpClient
คุณต้องการ (เช่นจำลองHttpClient
ไม่ใช่ของจริง)
ดังนั้นคุณจะมีโรงงานดังต่อไปนี้:
public interface IHttpClientFactory
{
HttpClient Create();
}
และการใช้งาน:
public class HttpClientFactory
: IHttpClientFactory
{
public HttpClient Create()
{
var httpClient = new HttpClient();
return httpClient;
}
}
แน่นอนคุณจะต้องลงทะเบียนในคอนเทนเนอร์ IoC ของคุณการใช้งานนี้ หากคุณใช้ Autofac มันจะเป็นดังนี้:
builder
.RegisterType<IHttpClientFactory>()
.As<HttpClientFactory>()
.SingleInstance();
ตอนนี้คุณจะมีการใช้งานที่เหมาะสมและทดสอบได้ ลองนึกภาพว่าวิธีการของคุณเป็นดังนี้:
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;
}
}
}
ตอนนี้ส่วนการทดสอบ HttpClient
ขยายออกไปHttpMessageHandler
ซึ่งเป็นนามธรรม มาสร้าง "จำลอง" HttpMessageHandler
ว่ายอมรับผู้รับมอบสิทธิ์เพื่อที่เมื่อเราใช้การจำลองเราสามารถตั้งค่าพฤติกรรมแต่ละอย่างสำหรับการทดสอบแต่ละครั้งได้
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);
}
}
และตอนนี้และด้วยความช่วยเหลือของ Moq (และ FluentAssertions ซึ่งเป็นไลบรารีที่ทำให้การทดสอบหน่วยอ่านง่ายขึ้น) เรามีทุกสิ่งที่จำเป็นในการทดสอบหน่วยวิธี PostAsync ของเราที่ใช้ 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);
}
}
}
เห็นได้ชัดว่าการทดสอบนี้โง่และเรากำลังทดสอบการเยาะเย้ยของเราจริงๆ แต่คุณได้รับความคิด คุณควรทดสอบตรรกะที่มีความหมายขึ้นอยู่กับการใช้งานของคุณเช่น ..
- หากสถานะรหัสของการตอบกลับไม่ใช่ 201 ควรมีข้อยกเว้นหรือไม่
- หากไม่สามารถแยกวิเคราะห์ข้อความตอบกลับจะเกิดอะไรขึ้น?
- เป็นต้น
คำตอบนี้มีจุดประสงค์เพื่อทดสอบบางสิ่งที่ใช้ HttpClient และนี่เป็นวิธีที่ดีในการทำเช่นนั้น
HttpClient
ในอินเทอร์เฟซของคุณคือจุดที่มีปัญหา คุณกำลังบังคับให้ลูกค้าของคุณใช้HttpClient
คลาสคอนกรีต แต่คุณควรเปิดเผยสิ่งที่เป็นนามธรรมของไฟล์HttpClient
.