With this post, I am starting a new series about how to perform unit and integration tests for Azure Blob Storage using Azurite Test Containers, Moq and xUnit. Over the time, I will updated this page with links to individual posts :

This Post - Getting started with testing for Azure Blob Storage : Dependency Injection

Getting started with testing for Azure Blob Storage : Unit Test with help of Moq

Getting started with testing for Azure Blob Storage : Unit Test with help of FakeItEasy (Alternative to MoQ)

Getting started with testing for Azure Blob Storage : Integration Test with help of TestContainers and Azurite

Getting started with testing for Azure Blob Storage : Mocking Azure Blob/File Storage SDK

Getting started with testing for Azure Blob Storage : Mocking Azure Blob/File Storage SDK with help of FakeItEasy (Alternative to MoQ)

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.

Please note, I am using .NET 7 console app to show in this post. But the process is same for any .NET version and any application type.

In fact, it’s difficult to introduce Dependency Injection in console app, so you can take it as Bonus that I am showing how to do it in console app.

Program.cs
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
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
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
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
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
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.