In part one of this guide I went over the basics of getting an Azure function up and running in Python. In part two I want to focus on getting adding tests. In this guide I will show you how to build and run unit tests. I’ll be jumping right in where we left off last time so if you need the code you can find it here.

Unit Testing:

Unit testing function apps in python is pretty much the same as unit testing in python anywhere else. Probably the only confusing part is figuring out what to pass in to your function to call it.

To start we’ll create a new folder in our src directory called tests. Inside we’ll create three new files __init__.py, hello_world_test.py, and blob_trigger_test.py. It’s important that the actual test files end in _test so that they get picked up by the test runner. After this your folder structure should look something like this:

src/
 ├── BlobTrigger/
 │   └── ...
 ├── HelloWorld/
 │   └── ...
 ├── tests/
 |   ├── __init__.py
 |   ├── hello_world_test.py
 |   └── blob_trigger_test.py
 └── ...

Next we will install pytest so that we can run our new tests. To install it simply type pip install pytest. With that out of the way let’s write some tests!

Hello World

To start we’ll add some boiler plate test stuff as well as some imports that we will need:

import unittest
import azure.functions as func

from HelloWorld import main # import the method we want to test
from unittest import mock

# Note how the class name starts with Test
class TestHelloWorld(unittest.TestCase):

  # Note how the test case starts with test_
  def test_hello_world(self):
    assert True == True

We can then run this test by opening a terminal/cmd, navigating to our src directory and then typing the command: pytest. This will automatically find our new test and run it and we should get a nice success message since as our test states True does in fact equal True.

Probably the only interesting this here so far is the naming convention that we must follow for the test class/test cases as noted in the code snippet above.

Next lets write our actual test.

The first step is to do some setup. If we look at our original hello world method we see that it takes two parameters: req and output_blob. For the request object it’s very straight forward we simply call func.HttpRequest and fill out the required parameters and everything is good.

import unittest
import azure.functions as func

from HelloWorld import main # import the method we want to test
from unittest import mock

# Note how the class name starts with Test
class TestHelloWorld(unittest.TestCase):

  # Note how the test case starts with test_
  def test_hello_world(self):
    # Arrange
    request = func.HttpRequest(
      method='GET',
      url='/api/helloworld',
      params={ 'name': 'chris' },
      body=None
    )

If instead of using parameters we wanted to test it using POST we would modify it like so:

# Make sure to import the json library at the top of the file: import json
request = func.HttpRequest(
      method='POST',
      url='/api/helloworld',
      body=json.dumps({
        'name': 'chris'
      }).encode('utf8')
    )

Next we create a mock for our output blob. This will allow us to later verify that the blob actually got created.

output_blob = mock.Mock()

After we have finished our setup we call the actual method under test, in this case main.

# Act
response = main(request, output_blob)

Finally we verify that everything went as expected. We start by verifying the status code is successful. Next we verify the response that the user gets back. The final thing we verify is that blob.set() was called which means that the blob actually got saved.

# Assert

# Assert we have a success code
assert response.status_code == 200

# Assert the response is as expected
assert 'Blob for user chris' in response.get_body().decode() 
    
# Assert that the blob actually got set
output_blob.set.assert_called()

Putting it all together our final test looks like this:

import unittest, json
import azure.functions as func

from HelloWorld import main # import the method we want to test
from unittest import mock

# Note how the class name starts with Test
class TestHelloWorld(unittest.TestCase):

  # Note how the test case starts with test_
  def test_hello_world(self):
    # Arrange
    request = func.HttpRequest(
      method='GET',
      url='/api/helloworld',
      params={ 'name': 'chris' },
      body=None
    )

    output_blob = mock.Mock()

    # Act
    response = main(request, output_blob)

    # Assert

    # Assert we have a success code
    assert response.status_code == 200

    # Assert the response is as expected
    assert 'Blob for user chris' in response.get_body().decode() 
    
    # Assert that the blob actually got set
    output_blob.set.assert_called()

And that’s all there is to it! If we wanted to we could create some additional test cases for when the user doesn’t provide a name. You can find some of these additional examples on the github repo for this guide which you can find linked down below.

Blob Trigger

Our blob trigger test will actually be very similar with a few small changes that we shall see in a second:

import unittest, json
import azure.functions as func

from BlobTrigger import main # import the method we want to test
from unittest import mock

class TestBlobTrigger(unittest.TestCase):
  @mock.patch('builtins.print')
  def test_blob_trigger(self, mock_print):
    # Arrange
    blob_data = 'here is some blob data'
    blob = func.blob.InputStream(data=blob_data.encode('utf8'), name='chris')

    # Act
    main(blob)

    # Assert
    calls = [
      mock.call(f'New blob created with name: chris'), 
      mock.call('Blob contents: here is some blob data')]

    # Check that print was called with our expected results
    mock_print.assert_has_calls(calls, any_order=True)

The first difference you’ll notice is that have an annotation at the top of our test method. This annotation is used to mock the print method so that we can verify that everything is working as expected. Anytime you need to mock a library you can use this method. Instead of builtins.print it would be library.method/object_you_want_to_mock.

Next we create a new blob to pass in and then call the test method.

After we call the test we create a list of the expected calls print should have. In this case we print the name and then print the contents. If order matters to calling a mocked method then you can change the any_order bool to False in the assert statement.

Final Thoughts

As you can see unit testing python function apps is fairly straight forward. Some of the blob trigger inputs can be a little tricky to find the right objects to pass in. I’ve tried to include a couple of the most common in this post but if you come across any that I havn’t gone over or would like to see more on feel free to drop a comment down below and i’ll try and update it. Thanks for reading and be sure to come back next time as I go over devops in azure ptyhon functions.

Further Reading & Resources

Leave a comment