Non sono convinto da molte delle risposte.
Prima di tutto, immagina di voler testare un metodo che utilizza HttpClient
. Non dovresti istanziare HttpClient
direttamente nella tua implementazione. Dovresti iniettare una fabbrica con la responsabilità di fornire un'istanza di HttpClient
per te. In questo modo puoi prendere in giro quella fabbrica in seguito e restituire quello HttpClient
che vuoi (es: un mock HttpClient
e non quello vero).
Quindi, avresti una fabbrica come la seguente:
public interface IHttpClientFactory
{
HttpClient Create();
}
E un'implementazione:
public class HttpClientFactory
: IHttpClientFactory
{
public HttpClient Create()
{
var httpClient = new HttpClient();
return httpClient;
}
}
Ovviamente dovresti registrare questa implementazione nel tuo contenitore IoC. Se usi Autofac sarebbe qualcosa del tipo:
builder
.RegisterType<IHttpClientFactory>()
.As<HttpClientFactory>()
.SingleInstance();
Ora avresti un'implementazione corretta e verificabile. Immagina che il tuo metodo sia qualcosa del tipo:
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;
}
}
}
Ora la parte di test. HttpClient
si estende HttpMessageHandler
, che è astratto. Creiamo un "mock" di HttpMessageHandler
che accetta un delegato in modo che quando usiamo il mock possiamo anche impostare ogni comportamento per ogni 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);
}
}
E ora, e con l'aiuto di Moq (e FluentAssertions, una libreria che rende gli unit test più leggibili), abbiamo tutto il necessario per unit test il nostro metodo PostAsync che utilizza 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);
}
}
}
Ovviamente questo test è stupido e stiamo davvero testando la nostra simulazione. Ma hai l'idea. Dovresti testare una logica significativa a seconda della tua implementazione come ..
- se lo stato del codice della risposta non è 201, dovrebbe generare un'eccezione?
- se il testo della risposta non può essere analizzato, cosa dovrebbe accadere?
- eccetera.
Lo scopo di questa risposta era testare qualcosa che usa HttpClient e questo è un modo semplice e pulito per farlo.
HttpClient
nella tua interfaccia è dove si trova il problema. Stai costringendo il tuo cliente a usare laHttpClient
classe concreta. Invece, dovresti esporre un'astrazione del fileHttpClient
.