【问题标题】:Mocking Fails for Azure Function (IHttpClientFactory)Azure 函数 (IHttpClientFactory) 的模拟失败
【发布时间】:2019-09-10 08:12:18
【问题描述】:

我在 Azure Function 中模拟 IHttpClientFactory 接口时遇到问题。这是我在做什么,我已经触发,一旦收到消息,我正在调用 API 来更新数据。我为此使用了 SendAsync 方法。在编写单元测试用例时,我无法模拟客户端。对于测试,我尝试在构造函数本身中进行 get 调用,但仍然无法正常工作。 函数类

 public class UpdateDB
{
    private readonly IHttpClientFactory _clientFactory;
    private readonly HttpClient _client;

    public UpdateDB(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
        _client = clientFactory.CreateClient();
        _client.GetAsync("");
    }

    [FunctionName("DB Update")]
    public async Task Run([ServiceBusTrigger("topic", "dbupdate", Connection = "connection")]string mySbMsg, ILogger log)
    {
        var client = _clientFactory.CreateClient();
        log.LogInformation($"C# ServiceBus topic trigger function processed message: {mySbMsg}");
        DBConvert payload = JsonConvert.DeserializeObject<DBConvert>(mySbMsg);
        string jsonContent = JsonConvert.SerializeObject(payload);
        var httpContent = new StringContent(jsonContent, Encoding.UTF8, "application/json");
        HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Post, "api/DBU/data");
        message.Content = httpContent;
        var response = await client.SendAsync(message);
    }
}

测试类

namespace XUnitTestProject1
{
    public class DelegatingHandlerStub : DelegatingHandler
    {
        private readonly Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> _handlerFunc;
        public DelegatingHandlerStub()
        {
            _handlerFunc = (request, cancellationToken) => Task.FromResult(request.CreateResponse(HttpStatusCode.OK));
        }

        public DelegatingHandlerStub(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> handlerFunc)
        {
            _handlerFunc = handlerFunc;
        }

        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            return _handlerFunc(request, cancellationToken);
        }
    }

    public class test
    {
        [Fact]
        public async Task Should_Return_Ok()
        {
            //
            Mock<ILogger> _logger = new Mock<ILogger>();
            var expected = "Hello World";
            var mockFactory = new Mock<IHttpClientFactory>();
            var configuration = new HttpConfiguration();
            var clientHandlerStub = new DelegatingHandlerStub((request, cancellationToken) =>
            {
                request.SetConfiguration(configuration);
                var response = request.CreateResponse(HttpStatusCode.Accepted);
                return Task.FromResult(response);
            });
            var client = new HttpClient(clientHandlerStub);

            mockFactory.Setup(_ => _.CreateClient(It.IsAny<string>())).Returns(client);

            var clientTest = mockFactory.Object.CreateClient();

            //Works Here, but not in the instance.
            clientTest.GetAsync("");

            IHttpClientFactory factory = mockFactory.Object;

            var service = new UpdateDB(factory);

            await service.Run("", _logger.Object);

        }
    }
}

我已按照此处的示例进行操作。 How to mock the new HttpClientFactory in .NET Core 2.1 using Moq

【问题讨论】:

  • 请您描述一下您遇到的问题,以及为什么您无法模拟HttpClient?此外,您正在调用 .GetAsync,但从不等待它完成(假设它是使用 async/await 的异步方法)。
  • 首先,我可以模拟 HttpClient,它只在测试项目中工作,当调用到达函数项目时,在构造函数中它不会创建模拟实例。模拟不起作用,它失败了。你使用异步是对的,我只是想找出问题所在。
  • 请重构问题中的代码。例如,为什么您在 UpdateDB 构造函数中创建 HttpClient 而不使用它?相反,您在 Run() 方法中创建另一个 HttpClient
  • @codebot 很好,你发现了这个问题。您一定收到过类似“Base url invalid format”之类的异常消息。它应该从“http://”开始
  • @codebot 添加对 u.test 如何失败的描述会很有帮助。我假设您的测试实现有问题,并建议了一种替代方法。

标签: c# unit-testing asp.net-core azure-functions moq


【解决方案1】:

对于模拟/拦截HttpClient 的使用,我建议你使用mockhttp 您的测试将是:

class Test {
  private readonly Mock<IHttpClientFactory> httpClientFactory;
  private readonly MockHttpMessageHandler handler;

  constructor(){
    this.handler = new MockHttpMessageHandler();
    this.httpClientFactory = new Mock<IHttpClientFactory>();
    this.httpClientFactory.Setup(_ => _.CreateClient(It.IsAny<string>()))
        .Returns(handler.ToHttpClient());
  }

  [Fact]
  public async Task Test(){
    // Arrange
    this.handler.Expect("api/DBU/data")
                .Respond(HttpStatusCode.Ok);
    var sut = this.CreateSut();

    // Act
    await sut.Run(...);

    // Assert
    this.handler.VerifyNoOutstandingExpectation();       
  }

  private UpdateDB CreateSut() => new UpdateDB(this.httpClientFactory.Object);
}

您可以进一步配置 HTTP 请求预期的行为方式,但为此您应该阅读mockhttp 的文档

【讨论】:

  • 作者mock HttpClient的方式比添加外部库要好很多
  • 想解释一下如何更好? IMO 我的回答中提出的方法更加惯用,并且不会暴露低级元素,例如HttpConfiguration。在测试项目中添加外部依赖对实际项目没有影响,所以我看不出添加外部库是个坏选择。
  • 因为在 .Net Core 中有很多简单直接的测试 HttpClient 的方法。我确定您引用的库最初是在测试模拟 HttpClient 非常困难时创建的。使用 HttpClientFactory 或键入/命名的 http 客户端,您可以以非常干净和灵活的方式对其进行测试
  • 这很可能是它存在的一个原因,当然,有很多方法可以做某事,但我的问题是你具体是如何确定这种方法并不比你没有的 OP 更好回答。澄清一下,这是关于如何编写测试的建议,我认为这是 OP 的主要关注点。
猜你喜欢
  • 2020-02-02
  • 2014-11-30
  • 1970-01-01
  • 2014-05-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多