visual studio – Unit Test for Azure Function HttpRequest parameter not detecting System.NotSupportedException – Stack Overflow


Background

I’m using MSTest and NSubstitute to build unit tests against some Azure Functions. These are .net 8, running in isolated mode. I’m working in VS2022 and using Test Explorer to run the tests.

Issue

My unit tests are working fine for testing my logic and exception handling, generally, but when I try to do an illegal operation against the HttpRequest object I pass into the function (req.Body.Length) the exception isn’t thrown during the test, much less caught by the test. The exception is thrown at runtime and is caught as would be expected. Can’t figure out why the exception doesn’t get thrown in the test.

How My Tests Are Set Up

Generally the unit tests are working as expected. I’m calling the Run method of the function, passing it an HttpRequest and FunctionContext, and testing return objects, exceptions, etc.

The signature of the run method is, for example:

        public async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
            FunctionContext functionContext)

Generally the tests are set up as follows:

            // Arrange
            HttpRequest httpRequest = Substitute.For<HttpRequest>();
            FunctionContext context = Substitute.For<FunctionContext>();
            httpRequest.Method = "POST";

            // Act
            IActionResult result = myFunction.Run(httpRequest, context).Result;

If I want to add a body, like a serialized object, I’ve been doing the following:

httpRequest.Body = new MemoryStream(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(myObject)));

In the function, I might have something like the following to deserialize the payload:

string requestBody = await new StreamReader(req.Body).ReadToEndAsync();

I’m able to test different scenarios of bad data in the object and all that, no problems so far.

The Problem

What I’m struggling with is that when I write a statement against the request stream that will cause a runtime exception, like the non-invocable req.Body.Length property, the tests don’t catch it – no exception is generated. This is executed in a try..catch block and definitely throws an exception at runtime, and I would expect it to violate expected response results in my tests (e.g. should be of type OkObjectResult). For instance, a null reference exception would be caught by my tests.

long test = req.Body.Length;

The runtime exception is System.NotSupportedException, with a message of “Specified method is not supported”.

What I’ve Tried

I’ve tried reworking how I’m submitting the request to be incredibly specific that Body should represent a stream:

var memStream = new MemoryStream();
var sw = new StreamWriter(memStream, Encoding.UTF8);

string body = JsonSerializer.Serialize(agentHeartbeat);

sw.Write(body);
sw.Flush();
memStream.Position = 0;
httpRequest.Headers.UserAgent = "testbot";
httpRequest.Body.Returns(memStream);

IActionResult result = myFunction.Run(httpRequest, context).Result;

I’ve also tried skipping the Substitute and just using DefaultHttpContext().Request:

            HttpRequest realReq = new DefaultHttpContext().Request;
            realReq.Method = "POST";
            realReq.Body = memStream;

I’ve also tried calling the Run method asynchronously as follows, with no change in behavior.

IActionResult result = await saveAgentHeartbeatFunction.Run(realReq, context);

I’ve stepped through the debugger while the test runs, and the sure enough, the length property returns an accurate value. No exception is generated. So there’s something screwy in how the test is reading that value – not treating it like a forward-only stream, but more like a string.

So in all these cases, no exception is thrown when querying the length during testing, even though it reliably does so in production. Any ideas on how these tests should be written (or test project reconfigured) so that the HttpRequest behaves more like the real thing?

Leave a Reply

Your email address will not be published. Required fields are marked *