1

I am facing issues while mocking the IHttpClientFactory Interface in Azure Function. Here is What am doing, I have trigger, once the message the received I am calling an API to update data. I am using SendAsync Method for that. While writing unit test case, I am not able to mock the client.For testing , I have tried making the get call in the constructor itself, still didn't work. Function Class

 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);
    }
}

TestClass

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);

        }
    }
}

I have followed the sample here. How to mock the new HttpClientFactory in .NET Core 2.1 using Moq

threeleggedrabbit
  • 1,722
  • 2
  • 28
  • 60
  • Please can you describe the issue you're experiencing, and also why you're unable to mock `HttpClient`? Also you're calling `.GetAsync` but never waiting for it to complete (assuming it is an async method using async/await). – ProgrammingLlama Sep 10 '19 at 08:14
  • To start with, I am able to mock the HttpClient, Its is working inside the Test Project only, The moment call reaches Functions project, in the constructor it doesn't create a mock instance. Mocking doesn't work it fails. you are right about using async, I am just trying to figure out what is the issue. – threeleggedrabbit Sep 10 '19 at 08:42
  • Please refactor your code in the question. Why for instance you create `HttpClient` in your `UpdateDB` constructor and don't use it? Instead, you create another `HttpClient` in your `Run()` method – OlegI Sep 10 '19 at 09:29
  • Okay, I figured out the issue, It is because internally we are creating actual HttpClient Instead of Mocking it. Hence, It is validating whether the passed URL is valid or not. So, If I pass valid URL(valid format) not the random string, test passes. – threeleggedrabbit Sep 10 '19 at 09:31
  • @OlegI: that I was doing for testing, Instead of making two calls to the function, I just want to validate whether it passes or not in the constructor itself. – threeleggedrabbit Sep 10 '19 at 09:32
  • 2
    @codebot good you figured the problem. You must have been getting the exception message like "Base url invalid format" . It should start from "http://" – OlegI Sep 10 '19 at 09:33
  • 1
    @codebot Adding a description of how your u.test is failing would had been helpful. I assumed that your test implementation was faulty and suggested an alternative approach to it. – Jota.Toledo Sep 10 '19 at 09:39

1 Answers1

2

For mocking/intercepting HttpClient usage, I would recommend you to use mockhttp Your test would then be:

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);
}

You can further configure how the HTTP request expectation should behave, but for that you should read a bit the documentation of mockhttp

Jota.Toledo
  • 27,293
  • 11
  • 59
  • 73
  • The way the author mocks the HttpClient is much better than adding an external library – OlegI Sep 10 '19 at 09:19
  • Care to explain how is it better? IMO the approach presented in my answer is a lot more idiomatic and does not expose low-level elements such as `HttpConfiguration`. Adding an external dependency into a test project has no effect on the actual project, so I dont see how adding an external library could be a bad choice. – Jota.Toledo Sep 10 '19 at 09:35
  • because of there are a lot of enough simple and straight forward ways of testing HttpClient in .Net Core. I'm sure the lib you referenced was initially created when back in time it was really difficult to test mock HttpClient. With HttpClientFactory or typed/named http clients you can test it in very clean and flexible way – OlegI Sep 10 '19 at 09:39
  • Thats most likely a reason for its existence and sure, there are a lot of ways to do something, but my question was concretely how you decided that this approach was not better than OPs, which you haven't answered. Just to clarify, this was a recommendation on how to write the test, which I assume was the main concern for OP. – Jota.Toledo Sep 10 '19 at 09:52