如何对HttpClient GetAsync进行单元测试



我的控制器中有以下内容:

public async Task<IActionResult> IndexAsync()
{
string baseUrl = "https://apilink.com";

using (HttpClient client = new HttpClient())
using (HttpResponseMessage response = await client.GetAsync(baseUrl))
using (HttpContent content = response.Content)
{
string data = await content.ReadAsStringAsync();
if (data != null)
{
var recipeList = JsonConvert.DeserializeObject<Recipe[]>(data);
return View();
}
}
return View();
}

我想对此进行单元测试,但无法解决如何测试HttpClient。

我试过:

[Test]
public void Index_OnPageLoad_AllRecipesLoaded()
{ 
var testController = new HomeController();
mockHttpClient.Setup(m => m.GetAsync(It.IsAny<string>())).Returns(
() => Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)));
mockHttpContent.Setup(m => m.ReadAsStringAsync()).Returns(() => Task.FromResult(LoadJson()));
var result = testController.IndexAsync();
Assert.IsNotNull(result); 
}
// Loads the Json data as I don't actually want to make the API call in the test. 
public string LoadJson()
{
using (StreamReader r = new StreamReader("testJsonData.json"))
{
string json = r.ReadToEnd();
return json;
}
}

有没有一种方法可以有效/简单地嘲笑这一点?或者我应该注入我自己的IHttpClient接口吗?(我不确定这是否是好的做法?(

感谢

有几种方法可以对HttpClient进行单元测试,但没有一种是直接的,因为HttpClient没有实现直接的抽象。

1( 编写抽象

这里有一个简单的抽象,您可以使用它来代替HttpClient。这是我推荐的方法。您可以将其注入到您的服务中,并像上面所做的那样使用Moq模拟抽象。

public interface IClient
{
/// <summary>
/// Sends a strongly typed request to the server and waits for a strongly typed response
/// </summary>
/// <typeparam name="TResponseBody">The expected type of the response body</typeparam>
/// <param name="request">The request that will be translated to a http request</param>
/// <returns>The response as the strong type specified by TResponseBody /></returns>
/// <typeparam name="TRequestBody"></typeparam>
Task<Response<TResponseBody>> SendAsync<TResponseBody, TRequestBody>(IRequest<TRequestBody> request);
/// <summary>
/// Default headers to be sent with http requests
/// </summary>
IHeadersCollection DefaultRequestHeaders { get; }
/// <summary>
/// Base Uri for the client. Any resources specified on requests will be relative to this.
/// </summary>
AbsoluteUrl BaseUri { get; }
}

此处提供完整的代码参考。Client类实现了抽象。

2( 创建一个假的Http服务器并验证服务器端的调用

这段代码设置了一个假服务器,您的测试可以验证Http调用。

using var server = ServerExtensions
.GetLocalhostAddress()
.GetSingleRequestServer(async (context) =>
{
Assert.AreEqual("seg1/", context?.Request?.Url?.Segments?[1]);
Assert.AreEqual("seg2", context?.Request?.Url?.Segments?[2]);
Assert.AreEqual("?Id=1", context?.Request?.Url?.Query);
Assert.AreEqual(headerValue, context?.Request?.Headers[headerKey]);
if (hasRequestBody)
{
var length = context?.Request?.ContentLength64;
if (!length.HasValue) throw new InvalidOperationException();
var buffer = new byte[length.Value];
_ = (context?.Request?.InputStream.ReadAsync(buffer, 0, (int)length.Value));
Assert.AreEqual(requestJson, Encoding.UTF8.GetString(buffer));
}
if (context == null) throw new InvalidOperationException();
await context.WriteContentAndCloseAsync(responseJson).ConfigureAwait(false);
});

此处提供完整的代码参考。

3( 模拟HttpHandler

您可以将MockHttpHandler注入到HttpClient中。这里有一个例子:

private static void GetHttpClientMoq(out Mock<HttpMessageHandler> handlerMock, out HttpClient httpClient, HttpResponseMessage value)
{
handlerMock = new Mock<HttpMessageHandler>();
handlerMock
.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(value)
.Verifiable();
httpClient = new HttpClient(handlerMock.Object);
}

完整的代码参考。然后,您可以验证mock本身的调用。

最新更新