Não estou convencido por muitas das respostas.
Em primeiro lugar, imagine que você deseja fazer um teste de unidade de um método que usa HttpClient. Você não deve instanciar HttpClientdiretamente em sua implementação. Você deve injetar uma fábrica com a responsabilidade de fornecer uma instância de HttpClientpara você. Dessa forma, você pode fazer o mock mais tarde naquela fábrica e retornar o HttpClientque quiser (por exemplo: um mock HttpCliente não o real).
Então, você teria uma fábrica como a seguinte:
public interface IHttpClientFactory
{
HttpClient Create();
}
E uma implementação:
public class HttpClientFactory
: IHttpClientFactory
{
public HttpClient Create()
{
var httpClient = new HttpClient();
return httpClient;
}
}
Claro, você precisaria registrar em seu contêiner IoC esta implementação. Se você usar o Autofac, seria algo como:
builder
.RegisterType<IHttpClientFactory>()
.As<HttpClientFactory>()
.SingleInstance();
Agora você teria uma implementação adequada e testável. Imagine que seu método seja algo como:
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;
}
}
}
Agora, a parte de teste. HttpClientestende HttpMessageHandler, que é abstrato. Vamos criar um "mock" de HttpMessageHandlerque aceita um delegado para que, ao usarmos o mock, possamos também configurar cada comportamento para cada teste.
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);
}
}
E agora, e com a ajuda do Moq (e FluentAssertions, uma biblioteca que torna os testes de unidade mais legíveis), temos tudo o que é necessário para testar nosso método PostAsync que usa HttpClient
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);
}
}
}
Obviamente, esse teste é bobo, e estamos realmente testando nosso mock. Mas você entendeu. Você deve testar uma lógica significativa, dependendo de sua implementação, como ..
- se o status do código da resposta não for 201, ele deve lançar uma exceção?
- se o texto da resposta não puder ser analisado, o que deve acontecer?
- etc.
O objetivo desta resposta foi testar algo que usa HttpClient e esta é uma maneira limpa e agradável de fazer isso.
HttpClientem sua interface é onde está o problema. Você está forçando seu cliente a usar aHttpClientclasse concreta. Em vez disso, você deve expor uma abstração doHttpClient.