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).
Hi, did you ended up doing next level (submitting an HTTP Request and getting back an HTTP Response with a JSON payload) ?
Hi Hari, so far one additional post has been published (Testing JSON Input/Output in Azure Functions Request Tests: https://spin.atomicobject.com/2020/07/22/test-json-azure-functions-request/), and another one has been written but not yet published (one about collecting all of the HttpTrigger-ed methods using reflection). I’m planning one additional post that ties those things all together to complete the project.