Moq:如何使用带有内部 HttpClient 的 Nunit 测试类
本文关键字:HttpClient Nunit 测试类 内部 何使用 Moq | 更新日期: 2023-09-27 18:30:52
我在 nUnit 中运行我的测试,通常我可以模拟出依赖项,然后返回某些值或抛出错误。
我有一个作为内部 HttpClient 的类,我想测试该类,我有什么选择。
这是我的代码,它不完整,以免淹没消息。如您所见,我在内部使用 HttpClient,而不是作为依赖项注入。该类抛出了许多自定义异常,我想对这些进行 Moq,否则我需要传递真实的用户名和密码,这些用户名和密码会给我抛出异常所需的状态代码。
有人有想法吗?如果我不能模拟 httpclient,那么我永远无法测试我的类是否引发异常。
我真的必须将 HttpClient 更改为对构造函数的依赖吗?
public bool ItemsExist(string itemValue)
{
var relativeUri = string.Format(UrlFormatString, itemValue.ToUpper());
var uri = new Uri(new Uri(this.baseUrl), relativeUri);
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", this.encodedCredentials);
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
var response = client.GetAsync(uri).Result;
switch (response.StatusCode)
{
case HttpStatusCode.Unauthorized:
// DO something here
throw new CustomAuthorizationException();
case HttpStatusCode.Forbidden:
throw new CustomAuthenticationException();
}
return true;
让我提出一个更简单的解决方案,不需要抽象/包装httpclient,我相信它与模拟框架完美配合。
你需要为假的HttpMessageHandler创建一个类,如下所示:
public class FakeHttpMessageHandler : HttpMessageHandler
{
public virtual HttpResponseMessage Send(HttpRequestMessage request)
{
throw new NotImplementedException("Rember to setup this method with your mocking framework");
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
return Task.FromResult(Send(request));
}
}
这种创建的 HttpMessageHandler 可以在实例化 HttpClient 时使用:
var msgHandler = new Mock<FakeHttpMessageHandler>() { CallBase = true };
var fakeclient = new HttpClient(msgHandler.Object);
您可以设置方法(此处使用最小起订量):
msgHandler.Setup(t => t.Send(It.Is<HttpRequestMessage>(
msg =>
msg.Method == HttpMethod.Post &&
msg.RequestUri.ToString() == "http://test.te/item/123")))
.Returns(new HttpResponseMessage(System.Net.HttpStatusCode.NotFound));
您现在可以在必要时使用假客户端。
像那样对它进行单元测试。就像你提到的:HttpClient是一个依赖项,因此,它应该被注入。
就个人而言,我会创建自己的IHttpClient
接口,由 HttpClientWrapper
实现,它环绕着System.Net.HttpClient
。 然后IHttpClient
将作为依赖项传递给对象的构造器。
如下所示,HttpClientWrapper
无法进行单元测试。但是,我会编写一些集成测试,以确保包装器编写良好。
编辑:
IHttpClient
不必是 HttpClient
的"有效"接口。它只需要是一个适合您需求的界面。它可以根据需要具有任意数量的方法。
想象一下:HttpClient允许你做很多事情。但是在你的项目中,你只调用GetAsync(uri).Result
方法,没有其他方法。
在此方案中,您将编写以下接口和实现:
interface IHttpClient
{
HttpResponseMessage Get(string uri);
}
class HttpClientWrapper : IHttpClient
{
private readonly HttpClient _client;
public HttpClientWrapper(HttpClient client)
{
_client = client;
}
public HttpResponseMessage Get(string uri)
{
return _client.GetAsync(new Uri(uri)).Result;
}
}
因此,正如我之前所说,界面只需要满足您的需求。您不必环绕整个 HttpClient 类。
显然,你会像这样对你的对象进行最小起订量:
var clientMock = new Mock<IHttpClient>();
//setup mock
var myobj = new MyClass(clientMock.object);
并创建一个实际对象:
var client = new HttpClientWrapper(new HttpClient());
var myobj = new MyClass(client );
编辑2
哦!而且别忘了IHttpClient
还应该扩展IDisposable
接口,非常重要!
另一种选择是使用 Flurl [披露:我是作者],一个用于构建和调用 URL 的库。它包括测试助手,使伪造所有HTTP变得非常容易。无需包装器接口。
对于初学者来说,你的HTTP代码本身看起来像这样:
using Flurl;
using Flurl.Http;
...
try {
var response = this.baseUrl
.AppendPathSegment(relativeUri)
.WithBasicAuth(username, password)
.WithHeader("Accept", "application/json")
.GetAsync().Result;
return true;
}
catch (FlurlHttpException ex) {
// Flurl throws on unsuccessful responses. Null-check ex.Response,
// then do your switch on ex.Response.StatusCode.
}
现在来看测试的乐趣:
using Flurl.Http.Testing;
...
[Test]
public void ItemsExists_SuccessResponse() {
// kick Flurl into test mode - all HTTP calls will be faked and recorded
using (var httpTest = new HttpTest()) {
// arrange
test.RespondWith(200, "{status:'ok'}");
// act
sut.ItemExists("blah");
// assert
test.ShouldHaveCalled("http://your-url/*");
}
}
在NuGet上获取它:
PM> Install-Package Flurl.Http