很多答案我都不相信。
首先,假设您想要对一个使用HttpClient的方法进行单元测试。您不应该在实现中直接实例化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,一个使单元测试更具可读性的库)的帮助下,我们拥有了对使用HttpClient的方法PostAsync进行单元测试所需的一切
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);
}
}
}
显然这个测试很愚蠢,我们实际上是在测试我们的mock。但你懂的。您应该根据您的实现测试有意义的逻辑,例如..
如果响应的代码状态不是201,它是否应该抛出异常?
如果不能解析响应文本,应该发生什么?
等。
这个回答的目的是测试一些使用HttpClient的东西,这是一种很好的干净的方法。
更新
最近,我在测试中使用了一个http构建器,在那里我可以轻松地注入我所期望的json响应。
public class HttpClientBuilder
{
private HttpMessageHandler _httpMessageHandler = new HttpClientHandler();
public HttpClientBuilder WithJsonResponse(HttpStatusCode httpStatusCode, string json, string contentType = "application/json")
{
var mockHttpMessageHandler =
new MockHttpMessageHandler(
(request, cancellation) =>
{
var responseMessage =
new HttpResponseMessage(httpStatusCode)
{
Content = new StringContent(json, Encoding.UTF8, contentType)
};
var result = Task.FromResult(responseMessage);
return result;
});
_httpMessageHandler = mockHttpMessageHandler;
return this;
}
public HttpClient Build()
{
var httpClient = new HttpClient(_httpMessageHandler);
return httpClient;
}
}
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);
}
}
因此,只要我有一个抽象的HttpClient,比如IHttpClientFactory,就像我上面建议的那样,在我的测试中,我可以做一些这样的事情
var httpClientFactoryMock = new Mock<IHttpClientFactory>();
var jsonResponse = "{\"hello world\"}";
var httpClient =
new HttpClientBuilder()
.WithJsonResponse(HttpStatusCode.OK, jsonResponse)
.Build();
httpClientFactoryMock
.Setup(x => x.Create())
.Returns(httpClient);
var httpClientFactory = httpClientFactoryMock.Object;
然后使用httpClientFactory。