Open dioum2touba opened 7 months ago
I have built myself a builder for this scenario:
public sealed class MockHttpRequestData : HttpRequestData
{
private readonly FunctionContext context;
public MockHttpRequestData(
FunctionContext context,
string body)
: base(context)
{
byte[] bytes = Encoding.UTF8.GetBytes(body);
Body = new MemoryStream(bytes);
this.context = context;
Cookies = new List<IHttpCookie>().AsReadOnly();
Identities = new List<ClaimsIdentity>();
}
public override Stream Body { get; }
public override HttpHeadersCollection Headers { get; } = [];
public override IReadOnlyCollection<IHttpCookie> Cookies { get; }
public override Uri Url { get; }
public override IEnumerable<ClaimsIdentity> Identities { get; }
public override string Method { get; }
public override HttpResponseData CreateResponse()
{
MockHttpResponseData response = new(this.context);
return response;
}
public void AddHeaderKeyVal(string key, string value)
{
Headers.Add(key, value);
}
public void AddQuery(string key, string value)
{
Query.Add(key, value);
}
}
public sealed class MockHttpResponseData(FunctionContext context) : HttpResponseData(context)
{
public override HttpStatusCode StatusCode { get; set; }
public override HttpHeadersCollection Headers { get; set; } = [];
public override Stream Body { get; set; } = new MemoryStream();
public override HttpCookies Cookies { get; }
}
public class MockHttpRequestDataBuilder
{
private readonly IServiceCollection requestServiceCollection;
private IInvocationFeatures invocationFeatures;
private IServiceProvider requestContextInstanceServices;
private FunctionContext functionContext;
private string rawJsonBody;
public MockHttpRequestDataBuilder()
{
this.requestServiceCollection = new ServiceCollection();
this.requestServiceCollection.AddOptions();
}
public MockHttpRequestDataBuilder WithDefaultJsonSerializer()
{
this.requestServiceCollection
.Configure<WorkerOptions>(workerOptions =>
{
workerOptions.Serializer =
new JsonObjectSerializer(
new JsonSerializerOptions
{
AllowTrailingCommas = true,
});
});
return this;
}
public MockHttpRequestDataBuilder WithCustomJsonSerializerSettings(
Func<JsonObjectSerializer> jsonObjectSerializerOptions)
{
this.requestServiceCollection.Configure<WorkerOptions>(
workerOptions => workerOptions.Serializer = jsonObjectSerializerOptions());
return this;
}
public MockHttpRequestDataBuilder WithRequestContextInstanceServices(
IServiceProvider requestContextInstanceServices)
{
this.requestContextInstanceServices = requestContextInstanceServices;
return this;
}
public MockHttpRequestDataBuilder WithInvocationFeatures(
IInvocationFeatures invocationFeatures)
{
this.invocationFeatures = invocationFeatures;
return this;
}
public MockHttpRequestDataBuilder WithFakeFunctionContext()
{
this.requestContextInstanceServices ??= this.requestServiceCollection.BuildServiceProvider();
this.functionContext =
new FakeFunctionContext(this.invocationFeatures)
{
InstanceServices = this.requestContextInstanceServices,
};
return this;
}
public MockHttpRequestDataBuilder WithRawJsonBody(string rawJsonBody)
{
this.rawJsonBody = rawJsonBody;
return this;
}
public MockHttpRequestData Build()
=> new(this.functionContext, this.rawJsonBody);
}
Usage:
MockHttpRequestData mockHttpRequest =
new MockHttpRequestDataBuilder()
.WithDefaultJsonSerializer()
.WithFakeFunctionContext()
.WithRawJsonBody(rawJsonBody)
.Build();
InvoicesFunction invoicesFunction = new(
invoicesService.Object,
createInvoiceRequestValidator.Object,
Mock.Of<ILogger<InvoicesFunction>>());
HttpResponseData response = await invoicesFunction.CreateInvoiceAsync(mockHttpRequest, id, CancellationToken.None);
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
@davidpetric , @dioum2touba how do you mock the InvocationFeatures
on the FunctionContext
?
@davidpetric , @dioum2touba how do you mock the
InvocationFeatures
on theFunctionContext
?
This is how I did it for authorization Middleware(this is an older test where I did not have the builder mentioned in the comment above).
[Theory]
[InlineAutoMoqData("http://localhost:7268/api/swagger/ui")]
[InlineAutoMoqData("http://localhost:7268/api/swagger.json")]
[InlineAutoMoqData("http://localhost:7268/api/openapi/v3.json")]
public async Task Invoke_SkipAuthorizationIfSwaggerUrl(string url)
{
// Arrange
HostBuilder hostBuilderTest = new();
hostBuilderTest.ConfigureFunctionsWebApplication(builder => builder.UseMiddleware<AuthorizationMiddleware>());
bool delegateCalled = false;
Task MockedFunctionExecutionDelegate(FunctionContext context)
{
delegateCalled = true;
return Task.CompletedTask;
}
FunctionExecutionDelegate functionExecutionDelegate = MockedFunctionExecutionDelegate;
Mock<IHttpRequestDataFeature> httpRequestDataFeature = new();
Mock<IInvocationFeatures> invocationFeatures = new();
await using MemoryStream memoryStream = new(Encoding.UTF8.GetBytes(RequestBodyRaw));
FakeFunctionContext fakeFunctionContext = new(invocationFeatures.Object);
TestHttpRequestData httpRequestData = new(fakeFunctionContext, memoryStream, RequestMethod, url);
httpRequestDataFeature
.Setup(x => x.GetHttpRequestDataAsync(It.IsAny<FunctionContext>()))
.ReturnsAsync(httpRequestData);
invocationFeatures.Setup(x => x.Get<IHttpRequestDataFeature>())
.Returns(httpRequestDataFeature.Object);
// Act
AuthorizationMiddleware middleware = this.host.Services.GetService<AuthorizationMiddleware>();
// Assert
middleware.Should().NotBeNull();
await middleware.Invoke(fakeFunctionContext, functionExecutionDelegate);
delegateCalled.Should().BeTrue();
}
@davidpetric do you have the FakeFunctionContext
class? I'm curious how you wire up the InvocationFeatures in the ctor.
@davidpetric do you have the
FakeFunctionContext
class? I'm curious how you wire up the InvocationFeatures in the ctor.
@udlose please see the below code:
public class FakeFunctionContext(IInvocationFeatures features, IDictionary<object, object> items = null) : FunctionContext
{
public override string InvocationId { get; }
public override string FunctionId { get; }
public override TraceContext TraceContext { get; }
public override BindingContext BindingContext { get; }
public override RetryContext RetryContext { get; }
public override IServiceProvider InstanceServices { get; set; }
public override FunctionDefinition FunctionDefinition { get; }
public override IDictionary<object, object> Items { get; set; } = items;
public override IInvocationFeatures Features { get; } = features;
}
Flagging this as potential candidate for a new sample
@davidpetric , @dioum2touba how do you mock the
InvocationFeatures
on theFunctionContext
?This is how I did it for authorization Middleware(this is an older test where I did not have the builder mentioned in the comment above).
[Theory] [InlineAutoMoqData("http://localhost:7268/api/swagger/ui")] [InlineAutoMoqData("http://localhost:7268/api/swagger.json")] [InlineAutoMoqData("http://localhost:7268/api/openapi/v3.json")] public async Task Invoke_SkipAuthorizationIfSwaggerUrl(string url) { // Arrange HostBuilder hostBuilderTest = new(); hostBuilderTest.ConfigureFunctionsWebApplication(builder => builder.UseMiddleware<AuthorizationMiddleware>()); bool delegateCalled = false; Task MockedFunctionExecutionDelegate(FunctionContext context) { delegateCalled = true; return Task.CompletedTask; } FunctionExecutionDelegate functionExecutionDelegate = MockedFunctionExecutionDelegate; Mock<IHttpRequestDataFeature> httpRequestDataFeature = new(); Mock<IInvocationFeatures> invocationFeatures = new(); await using MemoryStream memoryStream = new(Encoding.UTF8.GetBytes(RequestBodyRaw)); FakeFunctionContext fakeFunctionContext = new(invocationFeatures.Object); TestHttpRequestData httpRequestData = new(fakeFunctionContext, memoryStream, RequestMethod, url); httpRequestDataFeature .Setup(x => x.GetHttpRequestDataAsync(It.IsAny<FunctionContext>())) .ReturnsAsync(httpRequestData); invocationFeatures.Setup(x => x.Get<IHttpRequestDataFeature>()) .Returns(httpRequestDataFeature.Object); // Act AuthorizationMiddleware middleware = this.host.Services.GetService<AuthorizationMiddleware>(); // Assert middleware.Should().NotBeNull(); await middleware.Invoke(fakeFunctionContext, functionExecutionDelegate); delegateCalled.Should().BeTrue(); }
Would the middleware functionExecutionDelegate need to be configured differently if I was to want to execute it as if a function is actually being called?
In case this is of use to others, I have created a basic HttpRequestData builder for .NET 8:
This is based on a few similar issues and respositories, fulfilling some of the simpler use cases for integration/Moq testing with a few examples for basic use cases.
Anyone is welcome to use/adapt/copy whatever areas may help you.
@davidpetric I really liked your fluent builder which inspired the main format, this is the latest thread I found on the topic so hopefully your more advanced Mock middleware example and this collation helps anyone searching
Description
For using Moq, I share with you how to implement this solution using Moq to test Azure Function Isolated .Net 8