- Published on
How to write Integration Tests for Azure Table Storage
We have an application that is integrated with Azure Table Storage and now, we want to test the integration. Let's explore how we can efficiently test this integration in an isolated way.
When it comes to testing external component integrations, I find narrow integration testing to be an optimal solution. These tests are straightforward to create and manage, and they offer quick feedback in case of any issues, making them more efficient than broad integration tests.
If you are unfamiliar with Azure Table Storage please refer to these introductory posts:
Complete source code is available on my Github.
Creating a Repository
I assume that most of us would have a repository layer built on top of the azure table storage. Here's what mine looks like, and we are going to create tests for it.
public class EmployeeRepository
{
private readonly TableClient client;
private readonly ILogger<EmployeeRepository> logger;
public EmployeeRepository(IConfiguration config, ILogger<EmployeeRepository> logger)
{
client = new TableClient(config["TableStorage:ConnectionString"],
config["TableStorage:Tables:Employee"]);
this.logger = logger;
}
public async Task<bool> Add(Employee employee)
{
if (employee is null)
{
throw new ArgumentNullException(nameof(employee));
}
var employeeEntity = new EmployeeTableEntity(employee.Department,
employee.Id.ToString(), employee);
try
{
await client.AddEntityAsync(employeeEntity);
return true;
}
catch (Azure.RequestFailedException ex)
{
logger.LogError(ex, "Not able to save entity.");
return false;
}
}
public async Task<Employee?> Get(string department, Guid id)
{
var response = await client.GetEntityIfExistsAsync<EmployeeTableEntity>(department,
id.ToString());
if (!response.HasValue)
{
return null;
}
var e = response.Value;
return new Employee(e.Id, e.FirstName, e.LastName, e.City, e.Department);
}
public void CreateTable() => client.Create();
public void DeleteTable() => client.Delete();
}
As you can see, this repository is quite straightforward, with four methods: one for adding an employee and another for retrieving an employee. While the inclusion of the CreateTable()
and DeleteTable()
methods might seem unusual in a repository, their purpose is to improve the testability of the EmployeeRepository
class. Without these methods, we would have been forced to create them in the test class, or instantiate another object that had these methods, resulting in increased dependencies. I prefer objects that can be tested independently.
Creating the Test class
First, let's create the Test class. I'm using Xunit for testing, and in Xunit, the constructor and Dispose()
method act as setup and teardown methods. So, I'm creating and deleting resources within these methods, respectively. For every test, a new table will be generated, and once the test finishes, it will be removed.
public class EmployeeRepositoryTests : IDisposable
{
private readonly EmployeeRepository repository;
public EmployeeRepositoryTests()
{
repository = CreateRepository($"EmployeeTest{Guid.NewGuid().ToString("N")}");
repository.CreateTable();
}
private EmployeeRepository CreateRepository(string tableName)
{
var configValues = new Dictionary<string, string?>()
{
{ "TableStorage:ConnectionString", "<connection string>"},
{ "TableStorage:Tables:Employee", tableName }
};
var config = new ConfigurationBuilder()
.AddInMemoryCollection(configValues)
.Build();
using var loggerFactory = LoggerFactory.Create(builder => builder
.SetMinimumLevel(LogLevel.Debug)
.AddConsole());
return new EmployeeRepository(config, loggerFactory.CreateLogger<EmployeeRepository>());
}
public void Dispose()
{
repository.DeleteTable();
}
}
You might be wondering about the reason why I've added a GUID suffix to the table name. This is because table deletion in Azure operates asynchronously. Therefore, if we were to use the same name for each test, Azure could potentially encounter an error when we attempt to create the table while it is still in the process of being deleted.
Creating Tests
I've created two tests to test the save and retrieval behavior of the repository. Let's run these tests and check the result.
[Fact]
public async Task Should_save_the_employee()
{
var employee = new Employee(Guid.NewGuid(), "FirstName", "LastName", "City", "Department");
Assert.True(await repository.Add(employee));
}
[Fact]
public async Task Should_return_the_saved_employee()
{
var e1 = new Employee(Guid.NewGuid(), "FirstName", "LastName", "City", "Department");
await repository.Add(e1);
var e2 = await repository.Get(e1.Department, e1.Id);
Assert.NotNull(e2);
Assert.Equal(e1.Id, e2.Id);
}
Both tests ran successfully. At this point, you could add more tests but, I will leave it here.
Conclusion
In conclusion, we've explored how to efficiently test the integration of an application with Azure Table Storage in an isolated manner. We chose narrow integration testing approach which tests only the integration points, offering simplicity and quick issue identification.
By following the practices discussed here, we can confidently test the integration of Azure Table Storage with our application.