Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
210 lines (150 sloc) 11 KB
type title excerpt tags share date
post
Tip 196 - Testing Azure Functions
Learn how to test Azure Functions with unit tests and integration tests
azure
functions
testing
true
2019-05-04 19:00:00 -0700

::: tip 💡 Learn more : Azure Functions.

📺 Watch the video : How to test Azure Functions. :::

Testing Azure Functions

I love Azure Functions. I use them all the time to execute small pieces of code without having to worry about how they run. Azure Functions are what they sound like: functions that run in Azure. So pieces of code, that just run in Azure. And they run serverless, which means that they run and scale automatically, without you needing to do anything to make that work.

One of the things that I love most about Azure Functions is that they come with bindings and triggers out-of-the-box. With these, you can have a Function run when a new Blob is uploaded to Azure Storage and output a value to an Azure Storage Queue, without writing any of the plumbing code to deal with Azure Storage. These bindings and triggers are great and the fact that everything runs in Azure is awesome too. All of this does seem to make it difficult to create traditional unit- and integration tests for Azure Functions to make sure that you don't break them when you change your code.

Fortunately, it's not that hard to test Azure Functions. Especially now that you can create pre-compiled Azure Functions in Visual Studio. This means that all of the Azure Functions code can run locally. And that makes it testable. Let's take a look.

Creating Unit Tests for Azure Functions

Writing unit tests for Azure Functions is just like writing unit tests for any other piece of code. There is some logic, that has input and output parameters, and you need to simulate those.

This is what a typical Azure Function with a HTTP trigger looks like:

[FunctionName("HttpTrigger")]
public static async Task<IActionResult> RunAsync([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]HttpRequest req, TraceWriter log)
{
    log.Info("C# HTTP trigger function processed a request.");

    string name = req.Query["name"];

    string requestBody = new StreamReader(req.Body).ReadToEnd();
    dynamic data = JsonConvert.DeserializeObject(requestBody);
    name = name ?? data?.name;

    return name != null
        ? (ActionResult)new OkObjectResult($"Hello, {name}")
        : new BadRequestObjectResult("Please pass a name on the query string or in the request body");
}

To start creating unit tests, you first need a test project to put them in. You can create one by right-clicking on the solution file that contains the Azure Function and clicking Add > new Project. Now, select the MSTest test project, just like in the image below. This creates a project that you can use to create your tests in and run them.

(Create a test project in Visual Studio)

So if you want to create a unit test for the function that we've seen earlier, you need to replicate the behavior of the parameters that go into the function:

  • HttpRequest req
  • TraceWriter log

For a unit test, you wouldn't simulate the HttpTrigger as it doesn't matter how the function is triggered. The unit test is all about the logic inside the function.

So how would you simulate these input parameters? Well, you could do something like this for the HttpRequest object:

public HttpRequest HttpRequestSetup(Dictionary<String, StringValues> query, string body)
{
    var reqMock = new Mock<HttpRequest>();

    reqMock.Setup(req => req.Query).Returns(new QueryCollection(query));
    var stream = new MemoryStream();
    var writer = new StreamWriter(stream);
    writer.Write(body);
    writer.Flush();
    stream.Position = 0;
    reqMock.Setup(req => req.Body).Returns(stream);
    return reqMock.Object;
}

This code uses the Moq framework to mock the behavior of the HttpRequest object. When you mock an object, the mock object mimics the behavior of it, letting the code in the test interact with it as if it were real. So when you write unit tests for something like an Azure Function, you can mock the parameter objects by looking at what they do. Often you can find this out by looking at them in the object explorer in Visual Studio. Alternatively, you can find this out in the documentation.

And for the TraceWriter parameter, you could do something like this:

public class VerboseDiagnosticsTraceWriter : TraceWriter
{        
    public VerboseDiagnosticsTraceWriter() : base(TraceLevel.Verbose)
    {
            
    }
    public override void Trace(TraceEvent traceEvent)
    {
        Debug.WriteLine(traceEvent.Message);
    }
}

This actually writes the log messages using Debug.WriteLine() and by implementing the abstract Microsoft.Azure.WebJobs.Host.TraceWriter class. So you don't always have to mock objects. You just need to simulate the objects however you can.

So now, a unit test for the Azure Function can look like this:

[TestMethod]
public async Task Request_With_Query()
{
    var query = new Dictionary<String, StringValues>();
    query.TryAdd("name", "Michael");
    var body = "";

    var result = await HttpTrigger.RunAsync(req: HttpRequestSetup(query, body), log: log);
    var resultObject = (OkObjectResult)result;
    Assert.AreEqual("Hello, Michael", resultObject.Value);            
}

The test creates a HttpRequest by calling HttpRequestSetup and uses the LogWriter (which is the log parameter that you see). And it tests if what the function returns is equal to "Hello, Michael". You can check out the full code sample on GitHub. And you can run the test from Visual Studio, by clicking on the Test menu and clicking Run > All Tests.

(Run tests in Visual Studio)

Creating Integration Tests for Azure Functions

Integration tests are more difficult than unit tests. An integration test for an Azure Function should start the Azure Function runtime and perform all of he actions needed within the function. Currently, there isn't a great way to create and run integration tests for Azure Functions yet. Until that is available, you can make it work by using some hardcoded strings. Let's try that out.

We will test the same HttpTrigger function that we've tested with unit tests. This is the default code that you get when you create a new HttpTrigger Azure Function. Now, we want to create an integration test for the function. This means that we will start the function locally and send HTTP to it to trigger it and make it run. You can create the integration test in the same test project that we've created earlier for the unit tests.

The integration test will look like this:

[TestMethod]
public async Task HttpTrigger()
{
    StartHost();

    string name = "Michael";

    await SendHttpRequest(name);

    EndHost();
}

The first thing that we do is to start the function host with the StartHost() method.

private Process _host;

private void StartHost()
{
    const string filename =
        @"C:\Users\Michael\AppData\Local\AzureFunctionsTools\Releases\2.10.1\cli\func.exe";

    const string args = "host start";

    const string binDir = @"C:\Users\Michael\functions-unittesting-sample-master\DotNet\bin\Debug\netstandard2.0\bin";

    ProcessStartInfo hostProcess = new ProcessStartInfo
    {
        FileName = filename,
        Arguments = args,
        WorkingDirectory = binDir
    };

    _host = Process.Start(hostProcess);

    Thread.Sleep(5000);
}

We start the function host by calling func.exe with the arguments "host start" and the directory that contains the Azure Function dll. The path to func.exe can be different on your machine, so search for it to find your path. The same goes for the path to the function dll. Once the host process is started, I make the thread sleep for 5 seconds, giving the function 5 seconds to complete. I don't think that this is an elegant way to call the function, but it works for now. In the future, the tooling might provide for better ways to do this.

Once the runtime has started, I call SendHttpRequest and send along the string "Michael".

private async Task SendHttpRequest(string name)
{
    HttpClient client = new HttpClient();

    string json = $"{{\"Name\": \"" + name + "\"}";

    var content = new StringContent(json, Encoding.UTF8, "application/json");

    var response =
    await client.PostAsync(@"http://localhost:7071/api/HttpTrigger", content);

    Assert.AreEqual("Hello, Michael", response.Content.ReadAsStringAsync());

    await Task.Delay(5000);
}

This method builds a string that we'll send as Json, which contains the name "Michael". Next, we send this payload with a POST method of the HttpClient object to the address http://localhost:7071/api/HttpTrigger, which is the local address of the Azure Function. You can find your address by locally running your Azure Function. The address will show up in the command prompt that outputs the function information. Finally, I check if what the function returns, matches the string "Hello, Michael". And again, I give the function 5 seconds to respond. In this case, by calling await Task.Delay(5000)

And finally, I shut the function host process down:

private void EndHost()
{
    _host.CloseMainWindow();
    _host.Dispose();
}

And that's it! A very simple example of an integration test for Azure Functions. And you can run this test by running it from Visual Studio, for instance, by clicking on the Test menu and clicking Run > All tests. Azure Functions integration tests can quickly become more complicated. For instance when you have a different trigger, like a Queue-, or a Blob-trigger. And you can have output bindings as well. In those cases, you can simulate all of that behavior by actually calling those services, preferably development versions of them, like those in the local development storage.

Conclusion

You can test Azure Functions by creating unit and integration-tests for them. Unit tests are relatively easy as you mock the behavior of things that go in and out of your function (and everything in between). And if you ask me, you don't always have to create unit tests that test all of the input and output parameters of the function. You could just test whatever is inside the function, as that is the code that matters. Creating integration tests for Azure Functions is possible, but not pretty. It requires some hardcoded values, which isn't ideal, but you can make it work.

You can’t perform that action at this time.