使用Moq库模拟Xunit测试中HttpClient的GetAsync方法

8
我正在为这个简单的服务编写一个单元测试,它只是调用外部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);
            }

        }
    }
}

我的第一个问题是:似乎在模拟和测试这些服务方面并不是很常见,我想知道是否有一些特定的解释。

其次,我尝试编写一个简单的单元测试,但是我无法模拟 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>();

    }

}

这里有另一个例子:https://justsimplycode.com/2018/09/22/mocking-httpclient-sendasync-method/ - Matthew
2个回答

10
无论您使用HttpClient类中的哪种方法(例如GetAsync,PostAsync等),下面的代码都是您应该使用的。所有这些方法都是为程序员方便而创建的。它们所做的事情是使用HttpMessageHandler类的SendAsync方法。请注意不要修改HTML标签。
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;
    });

接着你可以这样使用:

var httpClient = new HttpClient(mockHttpMessageHandler.Object);
var result = await httpClient.GetAsync(url, cancellationToken);

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


1
非常感谢!在确定响应内容之前先读取请求内容,对于使我的测试工作起关键作用。也就是说,例如登录请求应该有不同的响应,与健康检查请求不同。 - rjacobsen0

2

首先设置您的Mock HttpMessageHandler,然后将其传递给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);

这是我用来模拟SendAsync的单元测试的修改版,但它应该非常相似。"Original Answer"翻译成"最初的回答"。

2
在设置HttpMessageHandler模拟时,它抛出异常:"成员HttpMessageHandler.GetAsync不存在"。 - Tarta
应该是 "SendAsync" 而不是 "GetAsync"。请检查我的回答。 - Petru Ritivoiu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接