Azure

Getting started with testing for Azure Blob Storage : Dependency Injection

Perform unit & integration tests for Azure Blob Storage: Dependency Injection

On this page

Introduction

Recently, I was working on a existing project where we were uploading some files to Azure Blob Storage. While working on this project, I was responsible for maintaining at least 80% code coverage. While started analyzing the code coverage,found that the way it was written, it’s not possible to write unit tests for it. So, I decided to refactor the code. In this post, I will start with a very basic example and will refactor it to make it testable.

The Problem

Let’s get started with a very basic example. In this example, we will upload a file to Azure Blob Storage. Below is the code snippet for the same.

Program.cs
using Azure.Storage.Blobs;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;


using IHost host = Host
    .CreateDefaultBuilder(args)
    .ConfigureAppConfiguration((context, config) =>
    {
        config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
              .AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json", optional: true);
    })
    .Build();



await UploadFileToAzBlob(host.Services);
await host.RunAsync();


static async Task UploadFileToAzBlob(IServiceProvider services)
{
    var config = services.GetRequiredService<IConfiguration>();
    var azBlobConnectionString = config.GetValue<string>("AzBlobSettings:ConnectionString");
    var azBlobContainerName = config.GetValue<string>("AzBlobSettings:ContainerName");
    var fileName = config.GetValue<string>("AzBlobSettings:FileName")!;

    using FileStream stream = new(fileName, FileMode.Open);
    
    BlobContainerClient container = new (azBlobConnectionString, azBlobContainerName);

    try
    {
        await container.CreateIfNotExistsAsync();

        var client = container.GetBlobClient(fileName);
        await client.UploadAsync(stream);
    }
    catch (Exception)
    {
        throw;
    }
}

We will focus on UploadFileToAzBlob method. This method is responsible for uploading a file to Azure Blob Storage. In this method, we are creating a BlobContainerClient object directly and then using it to upload the file.

Problem with this approach is that we are not able to mock BlobContainerClient object. So, we are not able to write unit tests for this method. Also, it is dependent on IServiceProvider , which is not a good practice and makes it difficult to write unit tests for this method.

The Solution

In this section, we will start refactoring the code to make it testable. The first step is to move the BlobContainerClient releated code. We will introduce a new interface IAzBlobService and move all blob releated reponsibities to it’s own class AzBlobService. Below is the code snippet for the same.

IAzBlobService.cs
using Azure.Storage.Blobs;
using Microsoft.Extensions.Configuration;

namespace SystemUnderTest.Interface;

public interface IAzBlobService
{
    Task<bool> UploadFileToAzBlob(string fileName);
}

public class AzBlobService : IAzBlobService
{
    private readonly string azBlobConnectionString;
    private readonly string azBlobContainerName;
    public AzBlobService(IConfiguration  configuration)
    {
        azBlobConnectionString = configuration.GetValue<string>("AzBlobSettings:ConnectionString")!;
        azBlobContainerName = configuration.GetValue<string>("AzBlobSettings:ContainerName")!;
    }
    public async Task<bool> UploadFileToAzBlob(string fileName)
    {
        using FileStream stream = new(fileName, FileMode.Open);
        BlobContainerClient container = new(azBlobConnectionString, azBlobContainerName);

        try
        {          
            await container.CreateIfNotExistsAsync();
            var client = container.GetBlobClient(fileName);
            await client.UploadAsync(stream);
            return true;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Unable to upload blob. Reason :{ex.Message}" );
            return false;
        }
    }
}

Let’s refractor the method UploadFileToAzBlob.

Program.cs
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using SystemUnderTest.Interface;

using IHost host = Host
    .CreateDefaultBuilder(args)
    .ConfigureServices((context, services) =>
    {
        services.AddSingleton<IAzBlobService, AzBlobService>();
    })
    .ConfigureAppConfiguration((context, config) =>
    {
        config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
              .AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json", optional: true);
    })
.Build();



await UploadFileToAzBlob(host.Services);
await host.RunAsync();


static async Task UploadFileToAzBlob(IServiceProvider services)
{
    var azBlobService = services.GetRequiredService<IAzBlobService>();
    var result = await azBlobService.UploadFileToAzBlob("samplefile.txt");

    if (result)
    {
        Console.WriteLine("File uploaded successfully");
    }
    else
    {
        Console.WriteLine("File upload failed");
    }
}

Now, our method is not directly depends on BlobContainerClient, we have delegated the responsibility to IAzBlobService. This makes it easy to write unit tests for this method. In layman’s terms, we have decoupled the code and it is called Dependency Injection.

However, our method still dependents on IServiceProvider. In our next post, we will see how to remove this dependency as well and will write our first unit test case.

You can find the source code here.

Comments

#TalkWithMe