Unit Testable Code Involving Azure Table Connection

Writing code that is testable is a good practice. In my very recent experience in a project involving TDD, I realized that writing unit tests ensures the code follows SOLID principles. With unit tests, you are enforced to decouple the components and make them highly cohesive.

Even though testing integrations such as Database connections, File I/O do fall under “Integration testing”, as a developer you should ensure the testability of the logic underneath. In the same previously mentioned project I have come-across a situation where I need to retrieve information from Azure Table Storage, perform some manipulations on it and present it in an API response. In this post I shall discuss how did I ensured the unit testability of the code I wrote with the help of the built in DI framework in ASP.NET Core.

Configure Cloud Table Service

In ASP.NET Core, void ConfigureServices(IServiceCollection services) is used to add services to the container. This is the place where we inject the dependency for the CloudTable which is a singleton object for the life time. So we shall use the following code:

services.AddSingleton(provider =>
{
  var settings = "connectionstring";
  CloudStorageAccount storageAccount = CloudStorageAccount.Parse(settings);
  CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
  CloudTable table = tableClient.GetTableReference("tablename");
  return table;
});

INJECT THE DEPENDENCY IN CONTROLLER

The below is how the dependency for the CloudTable is injected to the controller, its nothing new:

public partial class ForecastController : ControllerBase
{
  private readonly CloudTable _cloudTable;

  public ForecastController(CloudTable cloudTable)
  {
    _cloudTable = cloudTable;
  }
  
  [HttpGet]
  public IActionResult GetAllEntities()
  {
    var tableEntities = _cloudTable.ExecuteQuery(new TableQuery<MyTableEntity>()).ToList();
    
    // Some logic goes here to use the tableEntities
    // This logic needs to be tested
    
    return Ok(tableEntities);
  }
}

Unit Testing the Controller Action

To unit test, first we need to mock the CloudTable object’s ExecuteQuery behavior in this case. The CloudTable class has no parameter-less constructors but fortunately this is not a sealed class. So lets create a derived class and use that to mock the behavior (just to make the unit test code look simple).

public sealed class CloudTableMock : CloudTable
{
  public CloudTableMock() : base(new Uri("http://mockuri"))
  {
  }
}

Along with that lets write a test method to make sure GetAllEntities action must return more that one entities, if it they are passed – for example.

public void TableEntities_Should_Return_More_Than_Zero()
{
  var mockTable = new Mock<CloudTableMock>();
  mockTable
    .Setup(w => w.ExecuteQuery(It.IsAny<TableQuery<MyTableEntity>>(), 
      It.IsAny<TableRequestOptions>(), 
      It.IsAny<OperationContext>()))
    .Returns(new List<MyTableEntity> 
    { 
      new MyTableEntity 
      {
        PropertyOne= "propertyOne",
        PropertyTwo= "propertyTwo",
        PartitionKey= "3a17ee7f-ecca-4790-ab71-397dc91f3ec9",
        RowKey= "577ffeaf-bb42-4978-866b-316ba84d6f12",
        Timestamp= new DateTimeOffset(DateTime.Now),
        ETag= "W/\"datetime'2020-05-21T14%3A35%3A04.1064255Z'\""
      }
    });

  var controller = new ForecastController(mockTable.Object);
  var result = controller.GetAllEntities();

  Assert.NotNull(result);
  Assert.True(((result as OkObjectResult)?.Value as IList<MyTableEntity>)?.Count > 0);
}

The above is just an example to demonstrate the unit testability of the logic we’ve written involving CloudTable class with the help of ASP.NET Core DI framework. Like wise, you shall also mock the ExecuteAsync behavior to ensure the testability of the code that involves in writing the data back to the Azure Table Storage.

Notes: In my case, I had to write the Cloud Table Execute Query logic in the action itself. But this also can be written in a separate utility class as well, and that utility class also can be unit tested in the same manner.

Happy Coding!

9 thoughts on “Unit Testable Code Involving Azure Table Connection

  1. I followed this, changing to store the config data in a singleton, and the unit test works perfectly, however now when I hit the Get() endpoint, I now get a 404. Is there any other config for initialising the controller for regular execution?

    In my instance, it is an extension of an ODataController.

    I also noticed there are a lot of missing end brackets on your It.IsAny() statements.

    1. Steve, since the cloud instance was mocked in the unit test, it just works. Please ensure the cloud connection and the table name are correct in your actual implementation. It.IsAny didnt miss the end bracket, the ending parenthesis is located 2 lines after the opening parenthesis.

      1. Not the parenthesis, it’s missing all of these ‘>’

        I changed mockTable
        .Setup(w => w.ExecuteQuery(It.IsAny<TableQuery<MyTableEntity
        It.IsAny<TableRequestOptions,
        It.IsAny<OperationContext))
        .Returns(new List
        {
        . . .
        }

        to:

        mockTable
        .Setup(w => w.ExecuteQuery(It.IsAny<TableQuery>(),
        It.IsAny(),
        It.IsAny()))
        .Returns(new List
        {
        . . .
        }

        And that worked.

  2. Tested in the same version of Chrome – in which the code renders properly. Pretty weird issue though. However, I hope, you’re able to get the code from the image I sent in the earlier comment.

    1. Yeah, I was able to figure out what was wrong myself early today.

      My issue was with the endpoint not loading. Turned out to be an IIS cache issue I believe. Renaming the endpoint, and then back again and running it in between fixed it.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s