Integration Testing using the Azurite Storage Emulator

Pete Morton
Purplebricks Digital
8 min readJan 19, 2021

Pete Morton | Senior Full Stack Engineer at Purplebricks

At Purplebricks, we’re pretty good at writing unit tests to cover all scenarios for a particular functionality provided by a service or application, but integration testing is an area that, historically, we’ve been lacking in. You can check out the difference between unit tests and integrations tests if you need a little more background. This is something we’re consciously trying to improve upon, and I was recently writing a new web service which would benefit from a few integration tests.

The service in question is a simple web API for archiving old versions of documents, things like service agreements where occasionally there are changes to the service we provide as a company, and the existing document is superseded by a new version.

I won’t go into the detail of the actual API code, as that’s not the point of this post. Suffice to say, it’s a pretty simple API with a single POST endpoint, which when given a document, will upload it to a blob storage container.

If you’re not familiar with the term ‘blob’, it stands for Binary Large Object, and basically allows for large amounts of data to be stored without imposing any particular structure or data types.

Azure storage is Microsoft’s cloud storage solution, and it provides services for storing various types of data including files, tables and blobs. The goal of my new API was to simply archive documents for safe keeping, and Azure blob storage was a good fit for this purpose.

I wanted to write integration tests to ensure that my new archive service does indeed store the data it’s given into the desired Azure blob storage container.

So, how do we test against a real storage container?

For an integration test, we want to use ‘real’ services and data stores to ensure our code is doing what it should and working end-to-end, as opposed to using mocked versions as we might do in a unit test. But we don’t really want to use the actual blob storage container that our API will use in production (or even in our QA environment). This is still a test after all, and we don’t want to be filling our real cloud storage account with test data every time our integration tests run, potentially incurring costs. Also, if we were to use the real storage, there is the potential for unforeseen errors, for example if the account is temporarily inaccessible, which would affect the validity of our tests.

This is where Azurite comes in. Azurite is a lightweight free emulator aimed specifically at testing applications that use Azure storage locally, and it supersedes the Azure Storage Emulator. One big advantage of Azurite is that it can be run in a Docker container. This means for our integration tests, we can spin up a new instance of the Azurite blob storage service in Docker, and be sure of a clean environment that starts off in a predictable state every time we run our tests.

Planning the integration tests

Here’s a quick breakdown of what we want our integration tests to do:

  1. Start up our archive API.
  2. Configure the API so that it’s pointing to the Azurite blob storage emulator.
  3. Call the API, passing it some test data we want to archive.
  4. Attempt to retrieve the test data from the blob container.
  5. Confirm (assert) that the data was in the storage.

Step 2 is the crucial part here, where we want to spin up a new instance of the blob storage emulator so we can be sure our tests are starting with a clean, empty storage container, and as such we know that any data we find in there was put there by the test. So, let’s see how we can do this in practice.

Setting up the test infrastructure using IClassFixture

As already mentioned, for integration tests we want to run our actual API and have all the dependencies spun up as concrete services, rather than using mocking. To do this in C#, we create a test class that implements IClassFixture:

public class ArchiveTests :
IClassFixture<WebApplicationFactory<Startup>>

IClassFixture is part of the xUnit testing library and indicates a test class that can have some test data initialised for use by all tests in the class (see more about shared test content in xUnit). We specify the type of class fixture to be a WebApplicationFactory which we’ll use to bootstrap our archive service. This is a Microsoft factory class designed for instantiating an application in memory for testing. This WebApplicationFactory, in turn, needs to know the type of the entry point for the application or service it’s supposed to be spinning up, so for this we give it the Startup class of our archive API.

Enabling configuration changes with a custom web application factory

Because we want to change our service configuration to make use of the Azurite blob storage emulator, rather than using actual blob storage, we need a way to manipulate the service during app startup. We’ll replace the standard WebApplicationFactory with a custom factory that will give us hooks into the methods that get run automatically on startup:

public class CustomWebApplicationFactory<TStartup> :
WebApplicationFactory<TStartup> where TStartup : class

So our test class definition becomes:

public class ArchiveTests : 
IClassFixture<CustomWebApplicationFactory<Startup>>

Any tests we write inside this class will be able to use data and services configured in the CustomWebApplicationFactory, so now we can get down to the business of actually configuring this to use Azurite.

In our CustomWebApplicationFactory class, we can override the ConfigureWebHost method which is where app configuration occurs prior to the application actually starting up.

protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
...
});
}

In here, we want to remove the storage context that points to the real Azure blob storage, and replace it with a new context that points to the local storage emulator (Azurite).

// Remove the real Azure storage context registered in Startup
var storageContext = services.SingleOrDefault(s => s.ServiceType == typeof(IStorageContext));
if (storageContext != null)
{
services.Remove(storageContext);
}
// Add a new context using the Azure Storage Emulator (Azurite)
services.AddScoped<IStorageContext, StorageContext>(s =>
new StorageContext("UseDevelopmentStorage=true;"));

IStorageContext in the above code is a Purplebricks wrapper for Azure storage, which simplifies accessing storage containers.

"UseDevelopmentStorage=true;" is a shorthand that tells the storage context to look for Azure storage at the localhost address 127.0.0.1, which is where it should find Azurite running.

We now need to make sure Azurite is running. I mentioned earlier that Azurite can be run in docker, and for this to work locally we do obviously need to have docker installed first, but assuming it is, we can set up a System.Diagnostics.Process for running docker commands:

private static Process DockerProcess => new Process
{
StartInfo =
{
UseShellExecute = false,
FileName = "docker"
}
};

This allows us to run any commands we would normally use from the docker CLI, but will run them in-memory rather than using a command shell. To make this process run a command, we need to:

  1. Pass the command as arguments to the process (i.e. anything that would come after “docker” when running from the CLI);
  2. Start the process;
  3. Wait for the process to complete.

Since this applies for any command we might need to run, we can wrap these steps into an extension method:

public static void Execute(this Process process, string command)
{
process.StartInfo.Arguments = command;
process.Start();
process.WaitForExit(10000); // wait for 10 seconds
}

We now have a nice clean way of running a docker command. We actually only need 2 commands to get our Azurite storage emulator up and running. First, we need to pull down the latest Azurite docker image from the web:

DockerProcess.Execute("pull mcr.microsoft.com/azure-storage/azurite");

Second, spin up Azurite. By default we’ll get emulators for all the different types of Azure storage, but we’re only interested in blob storage for these integration tests, so we can specify this with a few command arguments:

DockerProcess.Execute("run -p 10000:10000 mcr.microsoft.com/azure-storage/azurite azurite-blob --blobHost 127.0.0.1 --blobPort 10000");

So, here we’re saying:

run Azurite, but only for blob storage and host it at 127.0.0.1, port 10000.

One final part of this setup is to make sure we clean up the Azurite docker container once all our integration tests have finished running. For this, we just need to stop the container in the Dispose method of our web application factory class:

DockerProcess.Execute("stop mcr.microsoft.com/azure-storage/azurite");

Pulling all this together, the CustomWebApplicationFactory class now looks like this:

The CustomWebApplicationFactory Class

Now, write some tests!

The final piece in the puzzle is the integration tests themselves. We can now make use of this CustomWebApplicationFactory to get the desired configuration for each test, such as pointing the test at the local Azurite blob storage emulator. So, let’s go back to the test class and write one.

As a reminder, the test class implements IClassFixture with a fixture type of CustomWebApplicationFactory , and the factory entry point for the application we want to bootstrap is our web API’s StartUp class:

public class ArchiveTests : 
IClassFixture<CustomWebApplicationFactory<Startup>>

Since we’ll be needing an instance of our CustomWebApplicationFactory for each test, we can pass this into the test class constructor and assign it to a local class-level variable:

private readonly CustomWebApplicationFactory<Startup> _factory;public ArchiveTests(CustomWebApplicationFactory<Startup> factory)
{
_factory = factory;
}

In the test plan set out earlier, step 1 was to start up the API. This essentially means asking our web application factory for an instance of an http client, and since the factory’s target entry point is our API’s Startup class, this will start the API and perform the custom configuration we’ve set up.

var httpClient = _factory.CreateClient();

Step 2 of our plan, to configure the API, is handled by the CustomWebApplicationFactory and has been adequately discussed already.

Step 3 is to call the API with some test data to archive. I’ve used helper methods to create some string data and serialise that into a JSON input model, but I’ll skip the detail of those since it’s not particularly relevant. We call these methods in the test, and pass the resulting content to our API:

var inputModel = CreateInputModel();
var content = CreateContent(inputModel);
var response = await httpClient.PostAsync(ApiUrl, content);
response.EnsureSuccessStatusCode();

Step 4 is to retrieve the test data from storage. For this, we need to again turn to our web application factory to get access to the Azurite storage context. We can create a local scope for instances of any services we need, so those don’t continue to use resources once we’ve finished with them, (see an explanation of scoped services for more context).

using var scope = _factory.Services.CreateScope();
var scopedServices = scope.ServiceProvider;
var storageSettings =
scopedServices.GetRequiredService<BlobStorageSettings>();
var storageContext =
scopedServices.GetRequiredService<IStorageContext>();

Once we have the storage context, we can attempt to retrieve the document. Our IStorageContext blob storage wrapper contains a GetBlob method which takes a blob container name and filename.

For simplicity, we’ll assume we’re always using the same filename when creating the document in the blob store—which we’ve defined in our storage settings:

var blob = await storageContext.GetBlob(
storageSettings.ContainerName,
storageSettings.fileName);

Finally, step 5 is to confirm that we have actually retrieved the document (blob), proving that it was stored in the first place and that our API works!

Assert.NotNull(blob);

The completed test

With a little refactoring to hide away the detail of retrieving the data from storage, the completed integration test looks like this:

Of course, we could write further tests to check specific details of what was stored, for example, by casting the blob back to the type of input model that we passed to our API and reading the properties. But keep in mind that integration tests can be heavy on resources, and as such we don’t want to write them for sake of it, especially if what we’re testing can be covered by unit tests.

So, that concludes how we can use the Azurite Storage Emulator and a custom “WebApplicationFactory” to facilitate writing integration tests that test code utilising blob storage. Whenever writing any tests, it’s important to consider whether integration or unit tests are the appropriate choice, as both will have their own benefits based on what you’re trying to achieve, and the environment in which you’re working.

--

--