原文地址: 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
来达到目的。
本篇源代码:https://github.com/lamondlu/Sample_TestHttpClient
ASP.NET Core中如何针对一个使用HttpClient对象的类编写单元测试
原文地址:https://www.cnblogs.com/lwqlun/p/10215929.html