Strategies for testing your code in Azure Functions

This article demonstrates how to create automated tests for Azure Functions.

Testing all code is recommended, however you may get the best results by wrapping up a Function's logic and creating tests outside the Function. Abstracting logic away limits a Function's lines of code and allows the Function to be solely responsible for calling other classes or modules. This article, however, demonstrates how to create automated tests against an HTTP and timer-triggered function.

The content that follows is split into two different sections meant to target different languages and environments. You can learn to build tests in:

The the sample repository is available on GitHub.

C# in Visual Studio

The following example describes how to create a C# Function app in Visual Studio and run and tests with xUnit.

Testing Azure Functions with C# in Visual Studio

Setup

To set up your environment, create a Function and test app. The following steps help you create the apps and functions required to support the tests:

  1. Create a new Functions app and name it Functions
  2. Create an HTTP function from the template and name it HttpTrigger.
  3. Create a timer function from the template and name it TimerTrigger.
  4. Create an xUnit Test app and name it Functions.Test.
  5. Reference the Functions app from Functions.Test app.

Create test classes

Now that the applications are created, you can create the classes used to run the automated tests.

Each function takes an instance of ILogger to handle message logging. Some tests either don't log messages or have no concern for how logging is implemented. Other tests need to evaluate messages logged to determine whether a test is passing.

The ListLogger class is meant to implement the ILogger interface and hold in internal list of messages for evaluation during a test.

Right-click on the Functions.Test application and select Add > Class, name it ListLogger.cs and enter the following code:

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions.Internal;
using System;
using System.Collections.Generic;
using System.Text;

namespace Functions.Tests
{
    public class ListLogger : ILogger
    {
        public IList<string> Logs;

        public IDisposable BeginScope<TState>(TState state) => NullScope.Instance;

        public bool IsEnabled(LogLevel logLevel) => false;

        public ListLogger()
        {
            this.Logs = new List<string>();
        }

        public void Log<TState>(LogLevel logLevel, 
                                EventId eventId,
                                TState state,
                                Exception exception,
                                Func<TState, Exception, string> formatter)
        {
            string message = formatter(state, exception);
            this.Logs.Add(message);
        }
    }
}

The ListLogger class implements the following members as contracted by the ILogger interface:

  • BeginScope: Scopes add context to your logging. In this case, the test just points to the static instance on the NullScope class to allow the test to function.

  • IsEnabled: A default value of false is provided.

  • Log: This method uses the provided formatter function to format the message and then adds the resulting text to the Logs collection.

The Logs collection is an instance of List<string> and is initialized in the constructor.

Next, right-click on the Functions.Test application and select Add > Class, name it LoggerTypes.cs and enter the following code:

namespace Functions.Tests
{
    public enum LoggerTypes
    {
        Null,
        List
    }
}

This enumeration specifies the type of logger used by the tests.

Next, right-click on the Functions.Test application and select Add > Class, name it TestFactory.cs and enter the following code:

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Internal;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Primitives;
using System.Collections.Generic;

namespace Functions.Tests
{
    public class TestFactory
    {
        public static IEnumerable<object[]> Data()
        {
            return new List<object[]>
            {
                new object[] { "name", "Bill" },
                new object[] { "name", "Paul" },
                new object[] { "name", "Steve" }

            };
        }

        private static Dictionary<string, StringValues> CreateDictionary(string key, string value)
        {
            var qs = new Dictionary<string, StringValues>
            {
                { key, value }
            };
            return qs;
        }

        public static DefaultHttpRequest CreateHttpRequest(string queryStringKey, string queryStringValue)
        {
            var request = new DefaultHttpRequest(new DefaultHttpContext())
            {
                Query = new QueryCollection(CreateDictionary(queryStringKey, queryStringValue))
            };
            return request;
        }

        public static ILogger CreateLogger(LoggerTypes type = LoggerTypes.Null)
        {
            ILogger logger;

            if (type == LoggerTypes.List)
            {
                logger = new ListLogger();
            }
            else
            {
                logger = NullLoggerFactory.Instance.CreateLogger("Null Logger");
            }

            return logger;
        }
    }
}

The TestFactory class implements the following members:

  • Data: This property returns an IEnumerable collection of sample data. The key value pairs represent values that are passed into a query string.

  • CreateDictionary: This method accepts a key/value pair as arguments and returns a new Dictionary used to create QueryCollection to represent query string values.

  • CreateHttpRequest: This method creates an HTTP request initialized with the given query string parameters.

  • CreateLogger: Based on the logger type, this method returns a logger class used for testing. The ListLogger keeps track of logged messages available for evaluation in tests.

Next, right-click on the Functions.Test application and select Add > Class, name it FunctionsTests.cs and enter the following code:

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Xunit;

namespace Functions.Tests
{
    public class FunctionsTests
    {
        private readonly ILogger logger = TestFactory.CreateLogger();

        [Fact]
        public async void Http_trigger_should_return_known_string()
        {
            var request = TestFactory.CreateHttpRequest("name", "Bill");
            var response = (OkObjectResult)await HttpFunction.Run(request, logger);
            Assert.Equal("Hello, Bill", response.Value);
        }

        [Theory]
        [MemberData(nameof(TestFactory.Data), MemberType = typeof(TestFactory))]
        public async void Http_trigger_should_return_known_string_from_member_data(string queryStringKey, string queryStringValue)
        {
            var request = TestFactory.CreateHttpRequest(queryStringKey, queryStringValue);
            var response = (OkObjectResult)await HttpFunction.Run(request, logger);
            Assert.Equal($"Hello, {queryStringValue}", response.Value);
        }

        [Fact]
        public void Timer_should_log_message()
        {
            var logger = (ListLogger)TestFactory.CreateLogger(LoggerTypes.List);
            TimerFunction.Run(null, logger);
            var msg = logger.Logs[0];
            Assert.Contains("C# Timer trigger function executed at", msg);
        }
    }
}

The members implemented in this class are:

  • Http_trigger_should_return_known_string: This test creates a request with the query string values of name=Bill to an HTTP function and checks that the expected response is returned.

  • Http_trigger_should_return_string_from_member_data: This test uses xUnit attributes to provide sample data to the HTTP function.

  • Timer_should_log_message: This test creates an instance of ListLogger and passes it to a timer functions. Once the function is run, then the log is checked to ensure the expected message is present.

Run tests

To run the tests, navigate to the Test Explorer and click Run all.

Testing Azure Functions with C# in Visual Studio

Debug tests

To debug the tests, set a breakpoint on a test, navigate to the Test Explorer and click Run > Debug Last Run.

JavaScript in VS Code

The following example describes how to create a JavaScript Function app in VS Code and run and tests with Jest. This procedure uses the VS Code Functions extension to create Azure Functions.

Testing Azure Functions with JavaScript in VS Code

Setup

To set up your environment, initialize a new Node.js app in an empty folder by running npm init.

npm init -y

Next, install Jest by running the following command:

npm i jest

Now update package.json to replace the existing test command with the following command:

"scripts": {
    "test": "jest"
}

Create test modules

With the project initialized, you can create the modules used to run the automated tests. Begin by creating a new folder named testing to hold the support modules.

In the testing folder add a new file, name it defaultContext.js, and add the following code:

module.exports = {
    log: jest.fn()
};

This module mocks the log function to represent the default execution context.

Next, add a new file, name it defaultTimer.js, and add the following code:

module.exports = {
    isPastDue: false
};

This module implements the isPastDue property to stand is as a fake timer instance.

Next, use the VS Code Functions extension to create a new JavaScript HTTP Function and name it HttpTrigger. Once the function is created, add a new file in the same folder named index.test.js, and add the following code:

const httpFunction = require('./index');
const context = require('../testing/defaultContext')

test('Http trigger should return known text', async () => {

    const request = {
        query: { name: 'Bill' }
    };

    await httpFunction(context, request);

    expect(context.log.mock.calls.length).toBe(1);
    expect(context.res.body).toEqual('Hello Bill');
});

The HTTP function from the template returns a string of "Hello" concatenated with the name provided in the query string. This test creates a fake instance of a request and passes it to the HTTP function. The test checks that the log method is called once and the returned text equals "Hello Bill".

Next, use the VS Code Functions extension to create a new JavaScript Timer Function and name it TimerTrigger. Once the function is created, add a new file in the same folder named index.test.js, and add the following code:

const timerFunction = require('./index');
const context = require('../testing/defaultContext');
const timer = require('../testing/defaultTimer');

test('Timer trigger should log message', () => {
    timerFunction(context, timer);
    expect(context.log.mock.calls.length).toBe(1);
});

The timer function from the template logs a message at the end of the body of the function. This test ensures the log function is called once.

Run tests

To run the tests, press CTRL + ~ to open the command window, and run npm test:

npm test

Testing Azure Functions with JavaScript in VS Code

Debug tests

To debug your tests, add the following configuration to your launch.json file:

{
  "type": "node",
  "request": "launch",
  "name": "Jest Tests",
  "program": "${workspaceRoot}\\node_modules\\jest\\bin\\jest.js",
  "args": [
      "-i"
  ],
  "internalConsoleOptions": "openOnSessionStart"
}

Next, set a breakpoint in your test and press F5.

Next steps

Now that you've learned how to write automated tests for your functions, continue with these resources: