Getting started with testing for Azure Blob Storage : Mocking Azure Blob/File Storage SDK
Hi Everyone!
This post is continuation of 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 :
Getting started with testing for Azure Blob Storage : Dependency Injection
Getting started with testing for Azure Blob Storage : Unit Test with help of Moq
This Post - Getting started with testing for Azure Blob Storage : Mocking Azure Blob/File Storage SDK
By far we have seen how to perform unit tests for Azure Blob Storage using Moq by hiding the dependency of Azure Storage SDK and actual implementation of it. But what if we want to test the actual implementation (i.e AzBlobService
) of Azure Storage SDK? In this post, we will see how to perform unit tests for Azure Blob Storage using Moq by mocking the Azure Storage SDK.
At the end of our First post, We hid the dependency of Azure Storage SDK and actual implementation of it by creating an interface IAzBlobService
and a class AzBlobService
which implements the interface. What if file upload fails due to blank file name or file name with invalid characters? In this case, we want to test the actual implementation of AzBlobService
and not the interface IAzBlobService
. So, we need to find a way to test the actual implementation of AzBlobService
.
Issue with Current Implementation
Currently AzBlobService
is dependent only on IConfiguration
and if you analyze further the below snippet, you will see that we are creating an instance of BlobServiceClient
at line 13.
1 |
|
Subsequently, we are using CreateIfNotExistsAsync
and UploadAsync
methods of BlobContainerClient
and BlobClient
respectively. It means that our current implementation is tightly coupled with Azure Storage SDK.
Solution
To solve this problem first, we need to make sure our implementation is loosely coupled with Azure Storage SDK.
We will refractor our code further and remove the dependency of IConfiguration
. But if remove the dependency of IConfiguration
, how will we get the connection string and container name?
Let’s create a class AzBlobSettingsOption
which will hold all the configuration details. and we will configure this class in Program.cs
file as shown below.
1 |
|
Now, we can inject the configuration whereever we want to use.
Next, we will install the package Microsoft.Extensions.Azure
which will help us to inject the depency of BlobServiceClient
. I guess this package is one of the underrated package😒. Below is the code snippet of Program.cs
1 |
|
Now, Let’s refractor the code for AzBlobService
which will now has a dependency of IAzureClientFactory and AzBlobSettingsOption instead of IConfiguration. Refractored version of AzBlobService
shown below -
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
public class AzBlobService : IAzBlobService
{
private readonly BlobServiceClient _blobServiceClient;
private readonly AzBlobSettingsOption _azBlobSettingsOption;
public AzBlobService(IAzureClientFactory<BlobServiceClient> blobServiceClientFactory, AzBlobSettingsOption azBlobSettingsOption)
{
_azBlobSettingsOption = azBlobSettingsOption;
_blobServiceClient = blobServiceClientFactory.CreateClient(_azBlobSettingsOption.ConnectionName);
}
public async Task<bool> UploadFileToAzBlobAsync(string fileName)
{
try
{
using FileStream stream = new(fileName, FileMode.Open);
BlobContainerClient container = _blobServiceClient.GetBlobContainerClient(_azBlobSettingsOption.ContainerName);
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;
}
}
}
This refractor will give us the flexibility to inject the mocked dependency of BlobServiceClient
while writing unit tests.
Writing Unit Tests
Finally, we have reached to the point where we can write unit tests for AzBlobService
. Let’s write the unit tests for AzBlobService
using Moq and xUnit. For this, I have created seperate class named AzBlobServiceTest
which will contain all the unit tests for AzBlobService
in our unit test project.
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
60
61
62
63
64
65
66
67
68
69
70
71
72
public class AzBlobServiceTest
{
private readonly StringWriter Output = new();
private const string _connectionName = "testconnection";
public AzBlobServiceTest()
{
Console.SetOut(Output);
}
[Fact]
public async Task File_Upload_Suceess()
{
// Arrange
var _azBlobServiceClientFactory = new Mock<IAzureClientFactory<BlobServiceClient>>();
var _azBlobServiceClient = new Mock<BlobServiceClient>();
var _azBlobContainerClient = new Mock<BlobContainerClient>();
var _azBlobClient = new Mock<BlobClient>();
var blobContainerInfo = BlobsModelFactory.BlobContainerInfo(default,default);
var blobContentInfo = BlobsModelFactory.BlobContentInfo(default,default,default,default,default);
_azBlobContainerClient.Setup(x => x.CreateIfNotExistsAsync(default,default,default,default)).ReturnsAsync(Response.FromValue(blobContainerInfo,default!));
_azBlobClient.Setup(x => x.UploadAsync(It.IsAny<Stream>(), default, default, default, default,default,default,default)).ReturnsAsync(Response.FromValue(blobContentInfo,default!));
_azBlobContainerClient.Setup(x => x.GetBlobClient(It.IsAny<string>())).Returns(_azBlobClient.Object);
_azBlobServiceClient.Setup(x => x.GetBlobContainerClient(It.IsAny<string>())).Returns(_azBlobContainerClient.Object);
_azBlobServiceClientFactory.Setup(x => x.CreateClient(_connectionName)).Returns(_azBlobServiceClient.Object);
var _azBlobSettingsOption = new AzBlobSettingsOption()
{
ConnectionName = _connectionName,
};
var _sut = new AzBlobService(_azBlobServiceClientFactory.Object, _azBlobSettingsOption);
// Act
var result = await _sut.UploadFileToAzBlobAsync("samplefile.txt");
// Assert
Assert.True(result);
}
[Fact]
public async Task File_Upload_Fail()
{
// Arrange
var _azBlobServiceClientFactory = new Mock<IAzureClientFactory<BlobServiceClient>>();
var _azBlobServiceClient = new Mock<BlobServiceClient>();
var _azBlobContainerClient = new Mock<BlobContainerClient>();
var _azBlobClient = new Mock<BlobClient>();
var blobContainerInfo = BlobsModelFactory.BlobContainerInfo(default,default);
var blobContentInfo = BlobsModelFactory.BlobContentInfo(default,default,default,default,default);
_azBlobContainerClient.Setup(x => x.CreateIfNotExistsAsync(default,default,default,default)).ReturnsAsync(Response.FromValue(blobContainerInfo,default!));
_azBlobClient.Setup(x => x.UploadAsync(It.IsAny<Stream>(), default, default, default, default,default,default,default)).ReturnsAsync(Response.FromValue(blobContentInfo,default!));
_azBlobContainerClient.Setup(x => x.GetBlobClient(It.IsAny<string>())).Returns(_azBlobClient.Object);
_azBlobServiceClient.Setup(x => x.GetBlobContainerClient(It.IsAny<string>())).Returns(_azBlobContainerClient.Object);
_azBlobServiceClientFactory.Setup(x => x.CreateClient(_connectionName)).Returns(_azBlobServiceClient.Object);
var _azBlobSettingsOption = new AzBlobSettingsOption()
{
ConnectionName = _connectionName,
};
var _sut = new AzBlobService(_azBlobServiceClientFactory.Object, _azBlobSettingsOption);
// Act
var result = await _sut.UploadFileToAzBlobAsync("");
// Assert
Assert.False(result);
Assert.Contains("Unable to upload blob. Reason", Output.ToString());
}
}
BlobServiceClient
and BlobContainerClient
and injecting it to AzBlobService
using AzBlobServiceClientFactory
. Also we tried to mock all the required methods of BlobServiceClient
and BlobContainerClient
which are used in AzBlobService
.
However, I would like to bring your attention to the line 19 and 20 of above code snippet, where we are using BlobsModelFactory
to create the instance of BlobContainerInfo
and BlobContentInfo
.
This factory class is very powerful and it can be used to create the instance of any class which is part of Azure.Storage.Blobs
namespace.
I have also created the scenario where file upload fails due to blank file name to show how to write negative unit tests.
Conclusion
In this article, we have learned how to write loosely coupled code for Azure Storage SDK and how to mock the Azure Storage SDK using Moq and xUnit. This whole series of articles was focused on Azure Blob Storage but you can use the same approach for Azure File Storage as well. For Azure File Storage, you just need to replace the BlobServiceClient
with FileServiceClient
and BlobContainerClient
with ShareClient
and BlobClient
with ShareFileClient
, and you are good to go. Also to mock the respose you will have FilesModelFactory
instead of BlobsModelFactory
.
You can find the source code here.