For what it's worth, this is my implementation using .NET 7 and Azure Functions v4. It is a workable, multi-request capable HttpClientFactory mock.
Unit Test Mock Setup
MockHttpClientFactory
public class MockHttpClientFactory
{
public static IHttpClientFactory Create(string name, MockHttpResponse response)
{
return Create(name, new List<MockHttpResponse> { response });
}
public static IHttpClientFactory Create(string name, List<MockHttpResponse> responses)
{
Mock<HttpMessageHandler> messageHandler = SendAsyncHandler(responses);
var mockHttpClientFactory = new Mock<IHttpClientFactory>();
mockHttpClientFactory
.Setup(x => x.CreateClient(name))
.Returns(new HttpClient(messageHandler.Object)
{
BaseAddress = new Uri("https://mockdomain.mock")
});
return mockHttpClientFactory.Object;
}
private static Mock<HttpMessageHandler> SendAsyncHandler(List<MockHttpResponse> responses)
{
var messageHandler = new Mock<HttpMessageHandler>(MockBehavior.Strict);
foreach(var response in responses)
{
messageHandler
.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync",
ItExpr.Is<HttpRequestMessage>(r => r.RequestUri!.PathAndQuery == response.UrlPart),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = response.StatusCode,
Content = (response.Response?.GetType() == typeof(string)
? new StringContent(response.Response?.ToString() ?? "")
: new StringContent(JsonSerializer.Serialize(response.Response)))
})
.Verifiable();
}
return messageHandler;
}
}
MockHttpResponse
public class MockHttpResponse
{
public MockHttpResponse()
{
}
public MockHttpResponse(string urlPart, object response, HttpStatusCode statusCode)
{
this.UrlPart = urlPart;
this.Response = response;
this.StatusCode = statusCode;
}
public string UrlPart { get; set; } = String.Empty;
public object Response { get; set; } = default!;
public HttpStatusCode StatusCode { get; set; } = HttpStatusCode.OK;
}
MockHttpRequestData
public class MockHttpRequestData
{
public static HttpRequestData Create()
{
return Create<string>("");
}
public static HttpRequestData Create<T>(T requestData) where T : class
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddFunctionsWorkerDefaults();
var serializedData = JsonSerializer.Serialize(requestData);
var bodyDataStream = new MemoryStream(Encoding.UTF8.GetBytes(serializedData));
var context = new Mock<FunctionContext>();
context.SetupProperty(context => context.InstanceServices, serviceCollection.BuildServiceProvider());
var request = new Mock<HttpRequestData>(context.Object);
request.Setup(r => r.Body).Returns(bodyDataStream);
request.Setup(r => r.CreateResponse()).Returns(new MockHttpResponseData(context.Object));
return request.Object;
}
}
MockHttpResponseData
public class MockHttpResponseData : HttpResponseData
{
public MockHttpResponseData(FunctionContext functionContext) : base(functionContext)
{
}
public override HttpStatusCode StatusCode { get; set; } = HttpStatusCode.OK;
public override HttpHeadersCollection Headers { get; set; } = new HttpHeadersCollection();
public override Stream Body { get; set; } = new MemoryStream();
public override HttpCookies Cookies { get; }
}
Usage
The Azure Function Method
This azure function has been setup with DI and uses an HttpClient object. Details are out of scope for this post. You can Google for more info.
public class Function1
{
private readonly HttpClient httpClient;
public Function1(IHttpClientFactory httpClientFactory)
{
this.httpClient = httpClientFactory.CreateClient("WhateverYouNamedIt");
}
[Function("Function1")]
public async Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequestData req)
{
var httpResponse = await this.httpClient.GetAsync("/some-path");
var httpResponseContent = await httpResponse.Content.ReadAsStringAsync();
// do something with the httpResponse or Content
var response = req.CreateResponse(HttpStatusCode.OK);
await response.WriteStringAsync(httpResponseContent);
return response;
}
}
Simple Use Case
public class UnitTest1
{
[Fact]
public void Test1()
{
var httpClientFactory = MockHttpClientFactory.Create("WhateverYouNamedIt", new MockHttpResponse());
var exception = Record.Exception(() => new Function1(httpClientFactory));
Assert.Null(exception);
}
}
More Realistic Use Case
[Fact]
public async Task Test2()
{
var httpResponses = new List<MockHttpResponse>
{
new MockHttpResponse
{
UrlPart = "/some-path",
Response = new { Name = "data" }
}
};
var httpClientFactory = MockHttpClientFactory.Create("WhateverYouNamedIt", httpResponses);
var httpRequestData = MockHttpRequestData.Create();
var function1 = new Function1(httpClientFactory);
var function1Response = await function1.Run(httpRequestData);
function1Response.Body.Position = 0;
using var streamReader = new StreamReader(function1Response.Body);
var function1ResponseBody = await streamReader.ReadToEndAsync();
Assert.Equal("{\"Name\":\"data\"}", function1ResponseBody);
}