在 Xunit 测试中使用 Moq 库模拟 HttpClient GetAsync

Tar*_*rta 5 c# unit-testing moq xunit asp.net-core

我正在为这个简单地调用外部 API 的小服务编写一个简单的单元测试:

public class ApiCaller : IApiCaller
{
    private readonly IHttpClientFactory _httpFactory;

    public ApiCaller(IHttpClientFactory httpFactory)
    {
        _httpFactory = httpFactory;
    }

    public async Task<T> GetResponseAsync<T>(Uri url)
    {
        using (HttpClient client = _httpFactory.CreateClient())
        {
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            client.Timeout = TimeSpan.FromSeconds(20);
            using (HttpResponseMessage response = await client.GetAsync(url))
            {
                response.EnsureSuccessStatusCode();
                string responseBody = await response.Content.ReadAsStringAsync();

                return JsonConvert.DeserializeObject<T>(responseBody);
            }

        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我的第一个问题是:模拟并因此测试此类服务似乎不是很常见的做法,我想知道是否有一些具体的解释。

其次,我尝试编写一个简单的单元测试,但我无法模拟GetAsync调用,因为 HttpClient 没有实现任何接口。

public class ApiCallerTest
{
    private readonly ApiCaller _target;
    private readonly Mock<IHttpClientFactory> _httpClientFactory;


    public ApiCallerTest()
    {
        _httpClientFactory = new Mock<IHttpClientFactory>();
        _target = new ApiCaller(_httpClientFactory.Object);
    }

    [Fact]
    public void WhenACorrectUrlIsProvided_ServiceShouldReturn()
    {


        var client = new HttpClient();
        _httpClientFactory.Setup(x => x.CreateClient(It.IsAny<string>())).Returns(client);

        var httpMessageHandler = new Mock<HttpMessageHandler>();

    }

}
Run Code Online (Sandbox Code Playgroud)

小智 8

无论您在 HttpClient 类中使用哪种方法(GetAsync、PostAsync 等),您都应该使用下面的代码。所有这些方法都是为了程序员的方便而创建的。他们所做的是使用HttpMessageHandler 类的SendAsync方法。

var mockHttpMessageHandler = new Mock<HttpMessageHandler>();

// Setup Protected method on HttpMessageHandler mock.
mockHttpMessageHandler.Protected()
    .Setup<Task<HttpResponseMessage>>(
        "SendAsync",
        ItExpr.IsAny<HttpRequestMessage>(),
        ItExpr.IsAny<CancellationToken>()
    )
    .ReturnsAsync((HttpRequestMessage request, CancellationToken token) =>
    {
        HttpResponseMessage response = new HttpResponseMessage();

        // configure your response here

        return response;
    });
Run Code Online (Sandbox Code Playgroud)

然后你这样使用它:

var httpClient = new HttpClient(mockHttpMessageHandler.Object);
var result = await httpClient.GetAsync(url, cancellationToken);
Run Code Online (Sandbox Code Playgroud)

您还可以在这里查看如何为 httpclient getasync 方法创建模拟?


Val*_*tor 3

首先设置你的 MockHttpMessageHandler并将其传递给你的HttpClient. 然后你可以为GetAsync处理程序上的方法设置一个 Mock,如下所示:

        var httpMessageHandler = new Mock<HttpMessageHandler>();

        // Setup Protected method on HttpMessageHandler mock.
        httpMessageHandler.Protected()
            .Setup<Task<HttpResponseMessage>>(
                "GetAsync",
                ItExpr.IsAny<string>(),
                ItExpr.IsAny<CancellationToken>()
            )
            .ReturnsAsync((HttpRequestMessage request, CancellationToken token) =>
            {
                HttpResponseMessage response = new HttpResponseMessage();

                // Setup your response for testing here.

                return response;
            });

        var client = new HttpClient(httpMessageHandler.Object);
Run Code Online (Sandbox Code Playgroud)

这是从我用来模拟的单元测试修改而来的SendAsync,但它应该非常相似。

  • 嗯,在设置 HttpMessageHandler 模拟时,它会抛出异常:“Member HttpMessageHandler.GetAsync 不存在” (2认同)