Accessing local settings while unit testing Azure Functions

There’s a lot to chew while unit testing Azure Functions. I going to be quite liberal with the terminology because technically some of this will be in fact integration testing and not unit testing per se.

Either way, Azure Functions load the local.settings.json on startup, creating several environment variables that then we can use in our code. In C# we would access them like so:

tenantId = configRoot["TenantId"];
appId = configRoot["AppId"];

or in Python:

tenantId = os.environ.get("TenantId");
appId = os.environ.get("AppId");

When unit testing the function, we are not running that startup code and end up with empty variables.

What we can do to solve this is to force the creation of said variables on the test start up.

In C# by dependency injection in the unit test project we can create a method on the StartUp.cs class that reads a configuration file and generates the variables for us:

[assembly: FunctionsStartup(typeof(Startup))]

namespace TestSendEmail;

public class Startup : FunctionsStartup
{
    /// <summary>
    /// Helper method to read configuration
    /// XUnit can't load the local.settings like if it was the Azure Function
    /// </summary>
    static void ConfigureEnvironmentVariablesFromLocalSettings()
    {
        var path = AppContext.BaseDirectory;
        var json = File.ReadAllText(Path.Join(path, "local.settings.json"));
        var parsed = JObject.Parse(json).Value<JObject>("Values");
        var connString = JObject.Parse(json).Value<JObject>("ConnectionStrings");

        foreach (var item in parsed) {
            Environment.SetEnvironmentVariable(item.Key, item.Value.ToString());
        }

        //Connection strings have a special prefix check the link below:
        //https://github.com/Azure/Azure-Functions/issues/717#issuecomment-400098791
        foreach (var item in connString) {
            Environment.SetEnvironmentVariable($"CUSTOMCONNSTR_{item.Key}", item.Value.ToString());
        }
    }

    public override void Configure(IFunctionsHostBuilder builder)
    {
        var serviceCollection = builder.Services;

        ConfigureEnvironmentVariablesFromLocalSettings();

        var configuration = new ConfigurationBuilder()
             .AddEnvironmentVariables()
             .Build();

        serviceCollection.AddSingleton(configuration);

        //Add your services here
        serviceCollection.AddSingleton<IStuff, StuffJob>();
        serviceCollection.AddSingleton<ILogger<StuffJob>, NullLogger<StuffJob>>();
    }
}

In Python we can do something similar by doing this:

class TestFunction(unittest.TestCase):
    def test_get_stuff(self):
        # Construct a mock HTTP request.
        req = func.HttpRequest(
            method='GET',
            body=None,
            url='/api/get_stuff'
        )

        #set variables from local.settings.json
        with open('local.settings.json') as lfile:
            localSettings = json.load(lfile)

        for i in localSettings['Values']:
            os.environ[i] = localSettings['Values'][i]

        # Call the function.
        resp = main(req)

        # Check the output.
        self.assertEqual(
            resp.status_code,
            200,
        )

        #clean the environment variables
        for i in localSettings['Values']:
            os.environ.pop(i,'None')

Using this strategy we can even have different settings when unit testing.

Have fun!

 Share!