2 Comments

Setting Up an Azure Functions Dependency Injection Context for Request Tests

I’ve been trying to figure out how to do some request testing on an Azure Functions project. My goal is to submit HTTP requests with JSON bodies, have them handled by the Functions running through as much of the real production code as possible, and then get back an HTTP response with a JSON payload. I want to run these tests as part of our normal xUnit test suite.

I’ve made some good progress, but I’m not all the way there yet. In this post, I want to share what I have so far for building up the dependency injection context for the Function class. This is as close as I’ve been able to get with the same type of dependency injection that would happen when the Function is run “for real.”

I’m pretty new to Azure Functions, so I’m figuring this stuff out as I go. If there’s a better way to do any of this, I’d love to hear about it!

Startup Services

It’s possible to register a “FunctionsStartup” class that will be used to initialize dependencies for an Azure Functions assembly. (Microsoft describes it here: Use dependency injection in .NET Azure Functions.)

The following shows how to include that Startup class in the test DI context.


var host = new HostBuilder()
  .ConfigureWebJobs(builder => builder.UseWebJobsStartup<Startup>()))
  .Build();

Configuration / Settings

My project loads configuration from the local.settings.json file when running locally and from Azure Application Settings when deployed.

The following declares a Dictionary with a few configuration settings and injects it such that it’s handled the same way the local.settings.json values (or the Azure Application Settings) would be handled in other environments.


var config = new Dictionary
{
  ["ApplicationEnvironment"] = "request-test",
  ["ApiHost"] = "fake-api.test.com",
};

var host = new HostBuilder()
  .ConfigureWebJobs(builder => builder.UseWebJobsStartup<Startup>())
  .ConfigureAppConfiguration((_, configBuilder) =>
    configBuilder.AddInMemoryCollection(config))
  .Build();

Logging

Troubleshooting request tests can be tricky. It can be very helpful to have access to your Function’s logs when trying to figure out what’s going wrong in a test.

Here I’m using the Foundatio.Logging.Xunit library, which is part of the extensive Foundatio set of libraries. It provides an ILoggerFactory implementation that uses xUnit’s ITestOutputHelper to write log statements out in such a way that xUnit can package them up with each test.


var host = new HostBuilder()
  .ConfigureWebJobs(builder => builder.UseWebJobsStartup<Startup>())
  .ConfigureAppConfiguration((_, configBuilder) =>
    configBuilder.AddInMemoryCollection(config))
  .ConfigureServices(services => services
    .AddLogging(builder =>
      builder.Services.AddSingleton(
        new TestLoggerFactory(testOutputHelper))))
  .Build();

The Function Class

Last but not least, if you’re going to call your Azure Function functions, you need an instance of the container class. We’ll add a line to register the GetUser Function class and then retrieve a properly instantiated instance of it.


var host = new HostBuilder()
  .ConfigureWebJobs(builder => builder.UseWebJobsStartup<Startup>())
  .ConfigureAppConfiguration((_, configBuilder) =>
    configBuilder.AddInMemoryCollection(config))
  .ConfigureServices(services => services
    .AddScoped<GetUser>()
    .AddLogging(builder =>
      builder.Services.AddSingleton(
        new TestLoggerFactory(testOutputHelper))))
  .Build();

var getUserFunction = host.Services.GetRequiredService<GetUser>();

Calling the Function

At this point, you have an instance of the Function, and you can call any of the methods on that object, just like any other object. There are a lot of examples of invoking an Azure Function in a test.


var getUserFunction = host.Services.GetRequiredService<GetUser>();

var result =
  await getUserFunction.GetUser(new DefaultHttpContext().Request);

What’s Next?

Next, I’m going to transition the way I’m currently calling the Function (invoking the method on an instance of the Function class) to make it closer to how it works in production (submitting an HTTP Request and getting back an HTTP Response with a JSON payload).