我的控制器中有以下内容:
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本身的调用。