C#模拟IHttpclient和CreateClient

4

我有一个函数需要进行单元测试,但似乎必须模拟CreateClient函数?每当我在测试期间进行调试时,似乎变量client等于null。我正确注入了依赖项,对此很确定。我想知道的是如何模拟CreateClient。

这里是该函数:

    public async Task CreateMessageHistoryAsync(Message message)
    {
        //This seems to be giving a null value
        var client = this.clientFactory.CreateClient(NamedHttpClients.COUCHDB);

        var formatter = new JsonMediaTypeFormatter();
        formatter.SerializerSettings = new JsonSerializerSettings
        {
            Formatting = Formatting.Indented,
            NullValueHandling = NullValueHandling.Ignore,
            ContractResolver = new CamelCasePropertyNamesContractResolver()
        };

        Guid id = Guid.NewGuid();            

        var response = await client.PutAsync(id.ToString(), message, formatter);

        if (!response.IsSuccessStatusCode)
        {
            throw new HttpRequestException(await response.Content.ReadAsStringAsync());
        }
    }

这里是单元测试,我正在一个单独的类中模拟IHttpClient,并使用该类。
    [Collection("MockStateCollection")]
    public class CreateMessageHistory
    {
        private readonly MockStateFixture mockStateFixture;

        public CreateMessageHistory(MockStateFixture mockStateFixture)
        {
            this.mockStateFixture = mockStateFixture;
        }

        [Fact]
        public async Task Should_NotThrowHttpRequestException_When_AMessageHistoryIsCreated()
        {
            var recipients = MockMessage.GetRecipients("Acc", "Site 1", "Site 2", "Site 3");
            var message = MockMessage.GetMessage(recipients);

            mockStateFixture
                .MockMessageHistoryService
                .Setup(service => service.CreateMessageHistoryAsync(message));

            var messageHistoryService = new MessageHistoryService(
                mockStateFixture.MockIHttpClientFactory.Object);

            mockStateFixture.MockIHttpClientFactory.Object.CreateClient("CouchDB");

            var task = messageHistoryService.CreateMessageHistoryAsync(message);
            var type = task.GetType();
            Assert.True(type.GetGenericArguments()[0].Name == "VoidTaskResult");
            Assert.True(type.BaseType == typeof(Task));
            await task;

            //await Assert.IsType<Task>(messageHistoryService.CreateMessageHistoryAsync(message));
            // await Assert.ThrowsAsync<HttpRequestException>(() => messageHistoryService.CreateMessageHistoryAsync(message));
        }
    }

似乎我还需要模拟CreateClient类,是吗?

你在哪个独立的类中进行模拟?我们需要看到您在该类中的操作。请发布代码。 - CodingYoshi
2个回答

6

您需要为已设置CreateClient方法的ClientFactory注入模拟对象。

// create the mock client
var httpClient = new Mock<IHttpClient>();

// setup method call for client
httpClient.Setup(x=>x.PutAsync(It.IsAny<string>()
                               , It.IsAny<Message>(),
                               , It.IsAny< JsonMediaTypeFormatter>())
          .Returns(Task.FromResult(new HttpResponseMessage { StatusCode = StatusCode.OK}));

// create the mock client factory mock
var httpClientFactoryMock = new Mock<IHttpClientFactory>();

// setup the method call
httpClientFactoryMock.Setup(x=>x.CreateClient(NamedHttpClients.COUCHDB))
                     .Returns(httpClient);

然后,您需要将 httpClientFactoryMock.Object 传递给构造函数:

var messageHistoryService = new MessageHistoryService(httpClientFactoryMock.Object);

更新

为了对HttpClient进行单元测试,由于它没有任何接口,您应该按照这里所述的方式进行包装。

具体来说,我们必须按以下方式安排http客户端:

// Mock the handler
var handlerMock = new Mock<HttpMessageHandler>(MockBehavior.Strict);

handlerMock.Protected()
// Setup the PROTECTED method to mock
           .Setup<Task<HttpResponseMessage>>("PutAsync",
                                             ItExpr.IsAny<String>(),
                                             ItExpr.IsAny<Message>()
                                             ItExpr.IsAny<MediaTypeFormatter>())
// prepare the expected response of the mocked http call
           .ReturnsAsync(new HttpResponseMessage()
           {
               StatusCode = HttpStatusCode.OK
           })
           .Verifiable();

// use real http client with mocked handler here
var httpClient = new HttpClient(handlerMock.Object)
{
    BaseAddress = new Uri("http://test.com/"),
};

现在当调用CreateClient方法时,我们应该返回上述的httpClient对象。

// create the mock client factory mock
var httpClientFactoryMock = new Mock<IHttpClientFactory>();

// setup the method call
httpClientFactoryMock.Setup(x=>x.CreateClient(NamedHttpClients.COUCHDB))
                     .Returns(httpClient);

没有IHttpClient,只有HttpClient,这是打字错误吗?我将其更改为HttpClient,但where部分//设置方法调用会出现错误,无法将"Moq.Mock<System.Net.Http.HttpClient>转换为'System.Net.HttpClient"。 - Ivan Apungan
@IvanApungan,我误以为你的客户端实现了一个接口并且这个接口就是实际的 HttpClient。请检查我的更新。链接将指导您如何模拟http客户端。因此,您应该仅更改上面的内容并使httpClientFactory返回此内容。如果有什么不清楚的地方,请告诉我。 - Christos
您真是个传奇!非常感谢您! - Ivan Apungan
1
@IvanApungan 非常欢迎!我很高兴能够帮助 :) - Christos
@Christos,你应该更新你的答案,提供正确的HttpClient和消息处理程序示例,因为没有IHttpClient。 - Nkosi
@Nkosi 更新了,如果您认为有遗漏或不清楚的地方,请告诉我。谢谢! - Christos

0
看起来自从接受的答案以来,IHttpMessageHandler已经发生了变化。在.NET6中,它有两个方法"Send"和"SendAsync"。所以我修改了@Christos之前的代码,并将其放入一个静态类中以供重用。
public static class MockHttpMessageHandler
{
    public static Mock<HttpMessageHandler> GetMessageHander(HttpResponseMessage response)
    {
        var handlerMock = new Mock<HttpMessageHandler>(MockBehavior.Strict);

        handlerMock.Protected()
            .Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
            .ReturnsAsync(response)
            .Verifiable();

        handlerMock.Protected()
            .Setup<HttpResponseMessage>("Send", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
            .Returns(response)
            .Verifiable();

        return handlerMock;
    }
}

使用非常简单,不需要创建一个模拟对象,只需直接实例化这个对象即可。
[Test]
public void Http_Get_Async_Was_Called()
{
    // Arrange
    var fakeResponse = new HttpResponseMessage(HttpStatusCode.OK)
    {
        Content = new StringContent(P24LocationTestCaseData.StringData),
    };
    var httpMessageHandlerMock = MockHttpMessageHandler.GetMessageHander(fakeResponse);
    var httpClientMock = new Mock<HttpClient>(httpMessageHandlerMock.Object);

    // Act
    // Use your httpClientMock here

    // Assert
    // Check that the HttpClient SendAsync was called once
    httpMessageHandlerMock
        .Protected()
        .Verify("SendAsync", Times.Once(), ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>());
}

我的上述实现显然返回一个字符串并响应任何方法(GET、POST、PUT等),但如果需要具体的修改,你可以进行修改。

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