Test Driven Development of Azure Functions with C# Part 2: Unit Tests
Tags: csharp, azure, dotnet, serverless
Published: 2021-03-08 07:08:05

In [Part 1 of this series](https://dev.to/sntnupl/test-driven-development-of-azure-functions-with-c-part-1-introduction-to-the-application-1pcl) we started to explore how to implement Test Driven Development of Serverless Azure Functions application. 
We had an overview of some of the Event Bindings that allow Azure Functions to integrate with Azure Services like Azure Blob Storage, Azure Service Bus, etc. 

In this post, we will see how we can add Unit Tests to our Azure Functions application.


**Other Posts in this series**

1. [Part 1 - Introduction to our sample Azure Functions application](https://dev.to/sntnupl/test-driven-development-of-azure-functions-with-c-part-1-introduction-to-the-application-1pcl)
2. Part 2 - This Article 
3. [Part 3 - Integration Tests](https://dev.to/sntnupl/test-driven-development-of-azure-functions-with-c-part-3-integration-tests-5116)

I have created this repository on Github, which contains all the associated source code used in this blog series:  

{% github sntnupl/azure-functions-sample-unittest-integrationtest no-readme %}


The relevant Code for this post is under **InvoiceProcessor.Tests.Unit** project in this repository.

&nbsp;  
___
### Adding Unit Test Project

We will be using xUnit Test Framework to implement Unit Tests for our Azure Function.  
You can visit [this official document](https://xunit.net/docs/getting-started/netcore/visual-studio), on how to get started with creating a new xUnit Test Project via Visual Studio.

Refer to `InvoiceProcessor.Tests.Unit` project within our [Github repository](https://github.com/sntnupl/azure-functions-sample-unittest-integrationtest), for all the relevant code discussed in this post.
File `InvoiceWorkitemEventHandlerShould.cs` hosts all the tests that we will use to Unit Test our Azure Function.


&nbsp;  
___
### Ground work

Before writing the test cases, we would need to do create some resources that we are going to need for our testing.  Let's take a look at them.

&nbsp;  
**Mocked Logger** 
We need to pass an instance of `ILogger` to our Azure Function. 
Going forward, let's address this as SUT. 

To aid in our testing, we have created our own implementation of `ILogger`.
This implementation is available in `ListLogger.cs` class within our project.

Some implementations of Unit Testing uses `NullLoggerFactory` to create an instance of `ILogger` and pass the same in the Azure Function.
That works for sure, but in our testing strategy, we intend to capture the logs generated by our SUT to validate sucess/failure of our test cases.
As such we have created `ListLogger`, which essentially stores all the published logs from SUT into a `List<string>`. Within our test cases we will be checking the contents of this list to Assert certain behaviors.

&nbsp;  
**Mocked AsyncCollector** 

Our SUT takes `IAsyncCollector<AcmeOrderEntry>` as its paramter. So we need to create a mock for this as well.
To aid in our testing, we have created our own implementation of `IAsyncCollector`, called `AsyncCollector` - you can refer to `AsyncCollector.cs` for the code.
This class essentially wraps over a `List<T>`  as well. Any invocation to `AddAsync()` will be adding items to this internal List.
This again, allows us to verify that SUT invoked `AddAsync()` properly on the `IAsyncCollector<AcmeOrderEntry>` that got passed to it.

&nbsp;  
**Other Mocks**

Apart from the above, we have mocked the following entities in our Test Project

- `IBinder`: this is passed as the method parameter to SUT
- `IAcmeInvoiceParser`
    - This is used within the SUT code, to parse the blob text.
    - To keep things simple, we have kept this as a publicly accessible static property within `InvoiceWorkitemEventHandler` class, instead of using Dependency Injection.

All these mocks is created within the constructor of our Test Suite, the `InvoiceWorkitemEventHandlerShould` class.
XUnit has few powerful strategies to allow developers share these resources as a *test context*. I would suggest going through the official xUnit documentation on [Shared Context between Tests](https://xunit.net/docs/shared-context), for more details.
To put things briefly, we wanted to recreate these mocks afresh for every test, hence we created them within the constructor of our Test Suite.


&nbsp;  
___
### Writing our first Unit Test

While writing these test cases, we must not loose sight of the fact that at the end of the day, an Azure Function is just a simple method which can be invoked as any normal function.

```csharp
public static async Task Run(
	[ServiceBusTrigger("workiteminvoice", "InvoiceProcessor", Connection = "ServiceBusReceiverConnection")] string msg,
	IBinder blobBinder,
	[Table("AcmeOrders", Connection = "StorageConnection")] IAsyncCollector<AcmeOrderEntry> tableOutput,
	ILogger logger)
```

So although we see the 1st parameter `string msg` decorated with the `ServiceBusTrigger` attribute, we dont need to do any extra work with respect to this attribute.
To simulate a message coming this Azure Function via the Service Bus, we just pass a string in the 1st parameter, thats it!

With that being said, lets go through one of the test cases which validates that an invalid message arriving via Service Bus would get rejected by our SUT.
This test is housed with `RejectInvalidMessagesFromServiceBus()` method of `InvoiceWorkitemEventHandlerShould` class.

&nbsp;  
**Arrange Part**

```csharp
public async Task RejectInvalidMessagesFromServiceBus()
{
    _mockParser
        .Setup(x => x.TryParse(
            It.IsAny<Stream>(),
            _testLogger,
            out _mockParsedOrders))
        .Callback(new TryParseCallback((Stream s, ILogger l, out List<AcmeOrder> o) => {
            o = new List<AcmeOrder>();
        }))
        .Returns(true);

    //...
}
```

Here we are setting up our mocked `IAcmeInvoiceParser`. We have used the wonderful [moq package](https://github.com/moq/moq4) to create these mocks.

This code essentially states that whenever SUT invokes `TryParse()` method on our mock, return a value of `true`.

- We dont really care about the `Stream` parameter that the SUT passes to `TryParse()` - `It.IsAny<Stream>()`
- But we do expect the SUT to pass our very own `ILogger` implementation (`ListLogger`) to be passed in `TryParse()` - `_testLogger`
- The `Callback()` part is to essentially mock-populate the `out` parameter that SUT will be passing to `TryParse()` method.

```csharp
public async Task RejectInvalidMessagesFromServiceBus()
{
	// ...

	_mockBlobBinder
	    .Setup(x => x.BindAsync<Stream>(
	        It.IsAny<BlobAttribute>(),
	        default))
	    .Returns(Task.FromResult<Stream>(null));

	var sut = new InvoiceWorkitemEventHandler();
	InvoiceWorkitemEventHandler.InvoiceParser = _mockParser.Object;

	// ...
}
```

Here we are setting up our mocked `IBinder` instance.
SUT can pass any `BlobAttribute`, and `default` value of `CancellationToken` (which is null), and our mock will return a `Task<stream>` with value `null`.

We have also created an instance of `InvoiceWorkitemEventHandler` class and injected our mocked `IAcmeInvoiceParser` to its public static property.


&nbsp;  
**Act Part**

```csharp
public async Task RejectInvalidMessagesFromServiceBus()
{
    // ...

   await InvoiceWorkitemEventHandler.Run(
		"Invalid work item",
		_mockBlobBinder.Object,
		_mockCollector,
		_testLogger);

    // ...
}
```

We invoke the SUT, with an invalid message `"Invalid work item"`.


&nbsp;  
**Assert Part**

```csharp
public async Task RejectInvalidMessagesFromServiceBus()
{
    //...

    var logs = _testLogger.Logs;

    logs.Should().NotBeNull();
    logs.Should().NotBeEmpty();
    logs.Should().NotContain(l => l.Contains("Empty Invoice Workitem."));
    logs.Should().Contain(l => l.Contains("Invalid Invoice Workitem."));
    _mockBlobBinder.Verify(b => b.BindAsync<Stream>(It.IsAny<BlobAttribute>(), default), Times.Never);
}
```

As we mentioned earlier, we will be leveraging our `ListLogger` class to verify SUT behavior.

Inside the SUT test code, we first check if the message from Service Bus is empty. If it is, the SUT logs message `Empty Invoice Workitem.` and exit.
Then it checks if the message from Service Bus is invalid. If it is it will log message `Invalid Invoice Workitem.` and exit.

What we have asserted here is that the first message is not logged by SUT, as message was not empty: `logs.Should().NotContain(l => l.Contains("Empty Invoice Workitem."));`
However the second message should get logged, becuase the message was an invalid `InvoiceWorkitemMessage`: `logs.Should().Contain(l => l.Contains("Invalid Invoice Workitem."));`

Lastly, we also assert that SUT must not invoke `BindAsync` on the `IBinder` passed to it, because the method should have exited.


&nbsp;  
___
Here, we have leveraged the [FluentAssertions package](https://github.com/fluentassertions/fluentassertions) to write the descriptive Assertions messages.

To execute test cases like these, use the Test Explorer in Visual Studio.  
![Visual Studio Test Explorer](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i1ow0t1v9lf2kn7o9ttx.jpg)

In the [last post of this series](https://dev.to/sntnupl/test-driven-development-of-azure-functions-with-c-part-3-integration-tests-5116), we will see how we can perform Integration Testing on our Azure Function.