TIP

🔥 The FREE Azure Developer Guide eBook is available here (opens new window).

💡 Learn more : Change feed in Azure Cosmos DB (opens new window).

This post was brought to you by Will Velida (opens new window).

# Unit testing the Azure Cosmos DB change feed in xUnit and C#

Writing unit tests for Azure Functions that are invoked by CosmosDB Triggers is quite simple.

In this article, we're going to set up a simple Azure Function that's triggered by the Azure Cosmos DB change feed and then setup and write a basic unit test for that function using xUnit (opens new window).

# What is the change feed?

The Azure Cosmos DB change feed is a persistent record of changes that take place in a container in the order that they occur. It listens to any changes in a container and then outputs a sorted list of documents that were changed in the order in which they were modified.

You can work with the Azure Cosmos DB change feed using either Azure Functions (opens new window) or with the change feed processor (opens new window).

# Writing our change feed Function

If you want to follow along, you'll need the following:

  • An Azure subscription (If you don't have an Azure subscription, create a free account (opens new window) before you begin)
  • An existing Azure Cosmos DB account with a database called PizzaParlourDB and two containers called Customers and CustomersAggregate. How you provision throughput isn't important.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Azure.Documents;
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using PizzaParlour.Core.Models;
using PizzaParlour.CustomerManager.Aggregate.Repositories;

namespace PizzaParlour.CustomerManager.Aggregate.Functions
{
    public class CustomerFeed
    {
        private readonly ILogger<CustomerFeed> _logger;
        private readonly ICustomerAggregateRepository _customerAggregateRepository;

        public CustomerFeed(
            ILogger<CustomerFeed> logger,
            ICustomerAggregateRepository customerAggregateRepository)
        {
            _logger = logger;
            _customerAggregateRepository = customerAggregateRepository;
        }

        [FunctionName(nameof(CustomerFeed))]
        public async Task Run([CosmosDBTrigger(
            databaseName: "PizzaParlourDB",
            collectionName: "Customers",
            ConnectionStringSetting = "CosmosDBConnectionString",
            LeaseCollectionName = "leases",
            CreateLeaseCollectionIfNotExists = true,
            LeaseCollectionPrefix = "Customers")]IReadOnlyList<Document> input)
        {
            try
            {
                if (input != null && input.Count > 0)
                {
                    foreach (var document in input)
                    {
                        var customer = JsonConvert.DeserializeObject<Customer>(document.ToString());

                        await _customerAggregateRepository.UpsertCustomer(customer);
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.LogError($"Exception thrown: {ex.Message}");
                throw;
            }            
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

This function does the following:

  • Listens to the ‘Customers’ container inside the ‘PizzaParlourDB’ database.
  • Creates a lease collection if it doesn’t exist. This controls the checkpoint of our change feed.
  • Creates a list of documents. This will be ordered in the order that the documents were modified.
  • If there are documents in the list, the Function iterates through each document, attempts to deserialize the document into a Customer object and then upserts that Customer into our read-optimized store.

# Writing our change feed Unit Test

Let’s write up a simple unit test for our change feed function:

using Microsoft.Azure.Documents;
using Microsoft.Extensions.Logging;
using Moq;
using Newtonsoft.Json;
using PizzaParlour.Core.Models;
using PizzaParlour.CustomerManager.Aggregate.Functions;
using PizzaParlour.CustomerManager.Aggregate.Repositories;
using PizzaParlour.CustomerManager.Aggregate.UnitTests.Helpers;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Xunit;

namespace PizzaParlour.CustomerManager.Aggregate.UnitTests.FunctionTests
{
    public class CustomerFeedShould
    {
        private Mock<ILogger<CustomerFeed>> _loggerMock;
        private Mock<ICustomerAggregateRepository> _customerAggregateRepoMock;

        private CustomerFeed _func;

        public CustomerFeedShould()
        {
            _loggerMock = new Mock<ILogger<CustomerFeed>>();
            _customerAggregateRepoMock = new Mock<ICustomerAggregateRepository>();

            _func = new CustomerFeed(
                _loggerMock.Object,
                _customerAggregateRepoMock.Object);
        }

        [Fact]
        public async Task UpsertNewDocument()
        {
            // Arrange
            var documentList = new List<Document>();
            var testCustomer = TestDataGenerator.GenerateCustomer();
            var customerDocument = ConvertCustomerObjectToDocument(testCustomer);
            documentList.Add(customerDocument);

            // Act
            await _func.Run(documentList);

            // Assert
            _customerAggregateRepoMock.Verify(
                r => r.UpsertCustomer(It.IsAny<Customer>()), Times.Once);
        }

        private Document ConvertCustomerObjectToDocument(Customer customer)
        {
            var customerJSON = JsonConvert.SerializeObject(customer);
            var document = new Document();
            document.LoadFrom(new JsonTextReader(new StringReader(customerJSON)));

            return document;
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

Let’s step through our UpsertNewDocument() test:

First, we create a new list of Documents. This will be our list that we use to invoke our Function. We then want to generate a test customer to add to our list. Before we can add this test customer to our list, we’ll need to convert it to a Document. For this purpose, I’ve got a private method that takes a Customer object, serializes it to JSON and then loads that JSON object into our Document and returns it.

Once our customer has been converted into a Document type, we then add it to our list. We then pass our list with our Customer document to invoke the function.

In my function, I have a Repository class that takes care of the Upsert logic for me, so I’m just verifying that the Repository method fires at least once, since I only have one Customer document in my list.

# Conclusion

This was a very basic example on how we can write unit tests for our change feed functions. Depending on how you are using and configuring change feed functions, your tests might need to be more comprehensive than this basic sample.

Hopefully this gives you some guidance on how you can write Unit tests for Azure Functions that are triggered by the change feed.