原文地址: 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使用的是HttpClientHandlerHttpClientHandler是一个处理程序,它向网络服务器发送请求并从服务器返回响应。在本篇博文中,我们将通过继承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实例化

测试服务

在下面的单元测试中,我们会使用XUnitFluentAssertionNSubstitute

测试场景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

最新文章

  1. 如何在mac本上安装android sdk
  2. GitHub Pages和每个项目绑定自定义域名(支持多个和顶级域名)
  3. NGUI Table页(UIToggle和UIToggledObjects)
  4. 解决ajax中文乱码问题
  5. 一张图看Goodle Clean设计架构
  6. 【转】 ios开发证书CER文件、P12文件,mobileprovition许可文件的用途
  7. Linux初始root密码设置
  8. IOS 性能优化的建议和技巧
  9. HttpClient模拟客户端请求实例
  10. win7下nsis打包exe安装程序教程
  11. kali linux 2.0下搭建DVWA渗透测试演练平台
  12. MySQL聚集索引和非聚集索引
  13. JavaScript中的事件模型
  14. SQLServer之数据类型解析
  15. Angular安装及创建第一个项目
  16. Python内置函数之classmetho staticmethod
  17. tee命令使用
  18. 【CF809E】Surprise me! 树形DP 虚树 数学
  19. Java8的lambda表达式和Stream API
  20. excel的宏与VBA实践——建表语句

热门文章

  1. Json的学习
  2. 2013年省赛I题 Thrall’s Dream
  3. Mix 导航
  4. 如何使用yql实现跨域访问
  5. React+Ant Design设置左侧菜单导航路由的显示与隐藏(与权限无关)
  6. docker 数据卷管理
  7. 了解vue APi
  8. 显存充足,但是却出现CUDA error:out of memory错误
  9. 【页面置换算法】LRC算法和FIFS算法
  10. boa调试