Writing unit tests for Azure Functions using C#
From my archive - originally published on 5 July 2017
[May 2019 Update] Well, it looks like Azure Functions finally got dependency injection out of the box. It's now pretty easy to add a decent test suite to an Azure Functions application. This (archived) blog post stands as a monument to the hoops we had to jump through and the hacks we had to hack to get Azure Functions apps working in the early days.
[Feb 2019 Update] The Azure Functions ecosystem is constantly evolving. When it first landed you were just pasting code into a text box. In early 2017 a toolset finally came along that allowed you to write compiled functions as .Net libraries using C#. This opened up the entire ecosystem of Visual Studio tools for code analysis, third party extensions and... unit testing.
Well, not quite. It's taken a while for things to mature in that direction. The lack of support for common code production techniques has been a real barrier to entry for teams considering "serverless" architecture. When it comes to unit testing, it is possible to provide coverage across an entire Azure Functions application, but there are one or two obstacles along the way.
Unit tests for static methods and dependencies
The main obstacle to unit testing Azure Functions is that they are static methods. This makes it very difficult to isolate dependencies. At the time of writing there is no direct support for dependency injection, though it is being considered for a future implementation.
Some community solutions have emerged to fill the gaps, including Boris Wilhelm's implementation that allows you to inject dependencies into method definitions. It leverages the Azure Functions 2.0 SDK's facility for creating custom bindings for function arguments. It uses Microsoft's standard Microsoft.Extensions.DependencyInjection library out of the box to manage the services. This can feel a little hacky as an input binding like this involves stringing together half a dozen classes to implement the patchily-documented bindings API.
It's worth noting that this style of method injection is not to everybody's taste as it can give rise to brittle unit tests that need to change whenever you re-arrange the dependencies. A more purist view of language constructs might also reserve attributes for declarative meta data rather than using them to implement behaviour.
The library is available as a NuGet package. You can inject dependencies directly into function signatures using an [Inject] attribute as shown below:
[FunctionName("Example")] public static async Task<IActionResult> Run( [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req, ILogger log, [Inject] IExample dependency) { // The dependency is injected by the IoC container }
Wiring up the dependencies is done by creating a custom Startup class as shown in the example below. Note that at the time of writing you’ll have to switch your functions project to run as a .Net Standard 2.0 library for this part to work. Presumably this will get fixed in a future release.
[assembly: WebJobsStartup(typeof(Startup))] namespace FunctionAppDI { internal class Startup : IWebJobsStartup { public void Configure(IWebJobsBuilder builder) => builder.AddDependencyInjection(ConfigureServices); private void ConfigureServices(IServiceCollection services) { services.AddTransient<IExample, ConcreteExample>(); } } }
An alternative approach for dealing with dependencies is to move any meaningful functionality out of the actual function definition and run it in a separate library. This means that the function definition is just a bootstrapper that reads configuration, wires up dependencies and handles exceptions. Any meaningful business logic is shunted into a separate library where it can be properly tested in isolation.
An advantage of this approach is that it helps make your implementation more portable, guarding against SDK lock-in. You can move the execution context to a service or API if you decide that Azure functions are not for you. You are also less vulnerable to the kind of breaking change that can often affect emerging platforms. The downside is that this increases complexity as you'll have to maintain a layer of "dumb" bootstrap functions on top of the libraries that do all the meaningful work in your system.
If you prefer to keep your functions isolated then you could consider setting up a shim that intercepts calls to any dependency, allowing you to insert your own test code. Microsoft Fakes is the best-known implementation for shims in .Net, but it is only available to Visual Studio Enterprise users. Other frameworks such as JustMock and TypeMock's Isolator can also provide shims but they both incur a license fee.
A final approach is to isolate dependencies behind singleton factory classes. These factories can return the correct dependency by default, though a unit test will be able to inject a mocked version into the function via the factory.
Stubbing Azure Functions trigger arguments
You can wire up Azure functions to a range of different triggers, and each of these give rise to different Run() method signatures. The majority of these are easy to incorporate into unit tests:
- Azure storage queue and event hubs just require message content
- Service bus functions can accept the message content or even a Message object from the service bus SDK
- Blob triggers accept a filename and a Stream object containing the content
Other trigger types require a little more work to prepare valid input arguments.
HTTP Triggers
HTTP-based triggers need you to mock out an HTTP request which is a little trickier. You will need to provide an HttpRequestMessage instance but also make sure that an HttpConfiguration instance is associated with it so you can create responses.
public static HttpRequestMessage CreateRequest(string json) { var request = new HttpRequestMessage { Method = HttpMethod.Post, RequestUri = new Uri("https://localhost"), Content = new StringContent(json, Encoding.UTF8, "application/json") }; request.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration()); return request; }
Note that to use HttpConfiguration your test project will need a reference to the Microsoft.AspNet.WebApi.Core NuGet package.
Timer Triggers
Functions that are triggered by a timer require a TimerInfo instance as an input argument. This is an abstract class so you will need to create your own stubbed version to pass it into a function during a unit test. This is a pretty simple implementation with a single method, i.e.
public class ScheduleStub : TimerSchedule { public override DateTime GetNextOccurrence(DateTime now) { throw new NotImplementedException(); } }