2 Comments

Testing JSON Input/Output in Azure Functions Request Tests

In my last post, Setting Up an Azure Functions Dependency Injection Context for Request Tests, I started working on request testing an Azure Functions project. The intent is to run through as much production code as possible.

Last time, I got the dependency injection context configured with the functions, configuration, and logging. In this post, I’m going to work on testing the Azure Functions’ handling of JSON input and output.

JSON Input

Azure Functions, at least in version 3.x, will automatically parse a JSON request body and pass the result in as an argument to the registered method.

Take the following Azure Function definition:


[FunctionName("CreateEntry")]
public async Task CreateEntryAsync(
  [HttpTrigger(AuthorizationLevel.Function, "post", Route = "entries")]
  HttpRequest req,
  CreateEntryInput input)
{
  ...
}

The framework will automatically parse the incoming request body into a CreateEntryInput instance. (My project is using the Newtonsoft Json.NET library.)


public class CreateEntryInput
{
  public string Description { get; set; }
  public string PartNumber { get; set; }
}

If I just instantiate an instance of CreateEntryInput and pass it to the CreateEntryAsync method in my test, I won’t be asserting that it will correctly parse the expected JSON input. For example, the documentation for the API says it accepts camelcase property names:


{
  "description": "5/16 inch bolt",
  "partNumber": "1234"
}

Without testing it, how can I be sure that the incoming JSON will be converted into the expected object correctly?

In order to get the same configuration that’s being used in the “real” Azure Function, we can get the JSON options from the dependency injection context:


var jsonBody = "{ \"description\": \"5/16 inch bolt\", \"partNumber\": \"1234\" }";
var jsonOptions =
  host.Services.GetService<IOptions<MvcNewtonsoftJsonOptions>>();

var input = JsonConvert.DeserializeObject(
  bodyText,
  typeof(CreateEntryInput),
  jsonOptions.Value.SerializerSettings);

var function = host.Services.GetRequiredService<MyAzureFunction>();

var request = new DefaultHttpContext().Request;
request.HttpContext.RequestServices = host.Services;

var actionResult =
  await function.CreateEntryAsync(request, input);

(See the earlier post for setting up the DI context.)

JSON Output

Just as I want to verify that my Azure Functions are properly parsing incoming JSON, I want my tests to verify that the response body contains the expected JSON.

Similar to the input, Azure Functions will automatically serialize the IActionResult returned from the bound method to JSON. This means that if you’re calling the bound method directly (as we are in a request test), the result will be an instance of IActionResult, not a JSON string.

To turn the IActionResult into JSON, we can instantiate an Microsoft.AspNetCore.Mvc.ActionContext and execute it, just as the real Azure Functions host framework will do in production.

Continuing from the code snippet above:



request.HttpContext.Response.Body = new MemoryStream();

if (!(actionResult is NoContentResult))
{
  var context = new ActionContext(
    request.HttpContext, 
    request.HttpContext.GetRouteData(), 
    new ActionDescriptor());

  await actionResult.ExecuteResultAsync(context);

  // Rewind the stream so it can be read on the next line
  request.HttpContext.Response.Body.Position = 0;

  var responseBodyJson =
    await new StreamReader(request.HttpContext.Response).ReadToEndAsync();

  ... // Assert responseBodyJson matches expectation
}

After executing the result, the response body stream can be read to get the resulting JSON text, and then assertions can be made.

Conclusion

I’m now able to test that if some expected JSON is passed into my method, I’ll get back the expected JSON, moving us closer to an end-to-end request test for an Azure Function, while still running in a normal “unit” test environment.