Je ne suis pas convaincu par la plupart des réponses.
Tout d'abord, imaginez que vous souhaitez tester unitaire une méthode qui utilise HttpClient
. Vous ne devez pas instancier HttpClient
directement dans votre implémentation. Vous devez injecter une usine avec la responsabilité de vous fournir une instance de HttpClient
. De cette façon, vous pouvez vous moquer plus tard de cette usine et retourner celle que HttpClient
vous voulez (par exemple: une maquette HttpClient
et pas la vraie).
Donc, vous auriez une usine comme celle-ci:
public interface IHttpClientFactory
{
HttpClient Create();
}
Et une mise en œuvre:
public class HttpClientFactory
: IHttpClientFactory
{
public HttpClient Create()
{
var httpClient = new HttpClient();
return httpClient;
}
}
Bien sûr, vous devrez enregistrer cette implémentation dans votre conteneur IoC. Si vous utilisez Autofac, ce serait quelque chose comme:
builder
.RegisterType<IHttpClientFactory>()
.As<HttpClientFactory>()
.SingleInstance();
Maintenant, vous auriez une implémentation correcte et testable. Imaginez que votre méthode ressemble à quelque chose comme:
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;
}
}
}
Maintenant, la partie test. HttpClient
s'étend HttpMessageHandler
, ce qui est abstrait. Créons un "simulacre" de HttpMessageHandler
qui accepte un délégué de sorte que lorsque nous utilisons le simulacre, nous pouvons également configurer chaque comportement pour chaque test.
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);
}
}
Et maintenant, et avec l'aide de Moq (et FluentAssertions, une bibliothèque qui rend les tests unitaires plus lisibles), nous avons tout ce qu'il faut pour tester unitaire notre méthode PostAsync qui utilise 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);
}
}
}
De toute évidence, ce test est idiot, et nous testons vraiment notre simulation. Mais vous voyez l'idée. Vous devez tester une logique significative en fonction de votre implémentation, telle que ..
- si le statut de code de la réponse n'est pas 201, doit-il lever une exception?
- si le texte de la réponse ne peut pas être analysé, que doit-il se passer?
- etc.
Le but de cette réponse était de tester quelque chose qui utilise HttpClient et c'est une belle façon propre de le faire.
HttpClient
dans votre interface est là où se situe le problème. Vous obligez votre client à utiliser laHttpClient
classe concrète. Au lieu de cela, vous devez exposer une abstraction duHttpClient
.