原文地址: How to unit test a class that consumes an HttpClient with IHttpClientFactory in ASP.NET Core?
作者: Anthony Giretti
譯者: Lamond Lu
介紹
幾年前,微軟引入了HttpClient
類來替代HttpWebRequest
來發送Web請求。這個新的類更易於使用,更加簡潔,更具有異步性,且易於擴展。
HttpClient
類有一個可以接受HttpMessageHandler
類對象的構造函數。HttpMessageHandler
類對象可以接受一個請求(HttpRequestMessage
), 並返回響應(HttpResponseMessage
)。它的功能完全取決於它的實現。默認情況下HttpClient
使用的是HttpClientHandler
,HttpClientHandler
是一個處理程序,它向網絡服務器發送請求並從服務器返回響應。在本篇博文中,我們將通過繼承DelegatingHandler
來創建自己的HttpMessageHandler
。
為了實現以上功能,HttpClient
對象不可以直接使用,而是需要與允許使用IHttpClientFactory
接口進行模擬的依賴注入一起使用。
讓我們來偽造一個HttpMessageHandler
下面的例子中,我們只討論HttpResponseMessage
, 不會處理HttpRequestMessage
。
以下是我偽造的一個HttpMessageHandler
對象。
public class FakeHttpMessageHandler : DelegatingHandler
{
private HttpResponseMessage _fakeResponse;
public FakeHttpMessageHandler(HttpResponseMessage responseMessage)
{
_fakeResponse = responseMessage;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return await Task.FromResult(_fakeResponse);
}
}
這里我添加了一個需要HttpResponseMessage
構造函數,然后復寫了SendAsync
方法, 在該方法中直接返回了構造函數中傳入的HttpResponseMessage
對象。
編寫一個使用IHttpClientFactory
接口的服務
下面我們需要編寫一個UserService
類,這個類提供了一個GetUsers
方法,來從遠程服務器端獲取用戶列表。
public class UserService
{
private readonly IHttpClientFactory _httpFactory;
public UserService(IHttpClientFactory httpFactory)
{
_httpFactory = httpFactory;
}
public async Task<List<User>> GetUsers(string url)
{
using (HttpClient httpclient = _httpFactory.CreateClient())
{
using (HttpResponseMessage response = await httpclient.GetAsync(url))
{
if (response.StatusCode == HttpStatusCode.OK)
{
List<User> users = await response.Content.ReadAsAsync<List<User>>();
return users;
}
return null;
}
}
}
}
以下是Api請求返回的用戶類
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
如你所見,使用HttpClientFactory
允許我們模擬HttpClient
實例化
測試服務
在下面的單元測試中,我們會使用XUnit
、FluentAssertion
、NSubstitute
測試場景1: 模擬一個請求,返回2個用戶
public class UserServiceTests
{
[Fact]
public async Task WhenACorrectUrlIsProvided_ServiceShouldReturnAlistOfUsers()
{
// Arrange
var users = new List<User>
{
new User
{
FirstName = "John",
LastName = "Doe"
},
new User
{
FirstName = "John",
LastName = "Deere"
}
};
var httpClientFactoryMock = Substitute.For<IHttpClientFactory>();
var url = "http://good.uri";
var fakeHttpMessageHandler = new FakeHttpMessageHandler(new HttpResponseMessage() {
StatusCode = HttpStatusCode.OK,
Content = new StringContent(JsonConvert.SerializeObject(users), Encoding.UTF8, "application/json")
});
var fakeHttpClient = new HttpClient(fakeHttpMessageHandler);
httpClientFactoryMock.CreateClient().Returns(fakeHttpClient);
// Act
var service = new UserService(httpClientFactoryMock);
var result = await service.GetUsers(url);
// Assert
result
.Should()
.BeOfType<List<User>>()
.And
.HaveCount(2)
.And
.Contain(x => x.FirstName == "John")
.And
.Contain(x => x.LastName == "Deere")
.And
.Contain(x => x.LastName == "Doe");
}
}
- 在以上測試中,我們期望獲取一個成功的響應,並得到2個用戶的信息。
- 我們期望從Service中得到的數據是JSON格式的。
- 我們使用一個偽造的處理程序初始化了一個
HttpClient
對象,然后定義了我們期望的得到的偽造對象httpClientFactoryMock.CreateClient().Returns(fakeHttpClient);
測試場景2: 模擬一個404錯誤,返回空數據
public class UserServiceTests
{
[Fact]
public async Task WhenABadUrlIsProvided_ServiceShouldReturnNull()
{
// Arrange
var httpClientFactoryMock = Substitute.For<IHttpClientFactory>();
var url = "http://bad.uri";
var fakeHttpMessageHandler = new FakeHttpMessageHandler(new HttpResponseMessage() {
StatusCode = HttpStatusCode.NotFound
});
var fakeHttpClient = new HttpClient(fakeHttpMessageHandler);
httpClientFactoryMock.CreateClient().Returns(fakeHttpClient);
// Act
var service = new UserService(httpClientFactoryMock);
var result = await service.GetUsers(url);
// Assert
result
.Should()
.BeNullOrEmpty();
}
}
- 和測試場景1類似,當一個Http請求返回Not Found, 它的結果集是Null
總結
本篇作者講解了在ASP.NET Core中如何偽造HttpClient
來測試持有HttpClient
對象的類。這里主要是通過偽造的DelegatingHandler
對象來創建一個HttpClient
對象,並使用IHttpClientFactory
來獲取偽造的HttpClient
來達到目的。