Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mocking CloudStorageAccount and CloudTable for Azure table storage

So I am trying to test Azure Table Storage and mock things that I depend on. My class is structured in a way that I establish a connection in the constructor, i.e. I create a new instance of CloudStorageAccount in which I create an instance of StorageCredentials that has storageName and storageKey. Afterwards, I create an instance of CloudTable, which I use further in the code to perform CRUD operations. My class looks as follows:

public class AzureTableStorageService : ITableStorage
{
        private const string _records = "myTable";
        private CloudStorageAccount _storageAccount;
        private CloudTable _table;

        public AzureTableStorageService()
        {
            _storageAccount = new CloudStorageAccount(new StorageCredentials(
                 ConfigurationManager.azureTableStorageName, ConfigurationManager.azureTableStorageKey), true);
            _table = _storageAccount.CreateCloudTableClient().GetTableReference(_records);
            _table.CreateIfNotExistsAsync();
        }

        //...
        //Other methods here
}

_table is reused throughout the class for different purposes. My goal is to mock it, but since it is virtual and doesn't implement any interface, I can't come over with a simple Mock solution like:

_storageAccount = new Mock<CloudStorageAccount>(new Mock<StorageCredentials>(("dummy", "dummy"), true));
_table  = new Mock<CloudTable>(_storageAccount.Object.CreateCloudTableClient().GetTableReference(_records));

Therefore, when I try to construct my Unit Test in this way I am getting: Type to mock must be an interface or an abstract or non-sealed class.

My goal is to accomplish something like:

_table.Setup(x => x.DoSomething()).ReturnsAsync("My desired result");

Any ideas are highly appreciated!

like image 964
Coke Avatar asked Nov 27 '18 23:11

Coke


People also ask

What is RowKey in Azure Table Storage?

The row key is a unique identifier for an entity within a given partition. Together the PartitionKey and RowKey uniquely identify every entity within a table. The row key is a string value that may be up to 1 KiB in size. You must include the RowKey property in every insert, update, and delete operation.

What type of storage is Azure Table storage?

Azure Table storage is a cloud-based NoSQL datastore you can use to store large amounts of structured, non-relational data. Azure Table offers a schemaless design, which enables you to store a collection of entities in one table. An entity contains a set of properties, and each property defines a name-value pair.

What are the elements of an Azure Table storage?

What are the elements of an Azure Table storage key? Table name and column name Partition key and row key O Row number 2. When should you use a block blob, and when should you use a page blob? Use a block blob for unstructured data that requires random access to perform reads and writes.


3 Answers

I was also struggling with implementing unit test for an Azure Function with a binding to Azure Table Storage. I got it finally working using a derived CloudTable class where I can override the methods I use and return fixed results.

/// <summary>
/// Mock class for CloudTable object
/// </summary>
public class MockCloudTable : CloudTable
{

    public MockCloudTable(Uri tableAddress) : base(tableAddress)
    { }

    public MockCloudTable(StorageUri tableAddress, StorageCredentials credentials) : base(tableAddress, credentials)
    { }

    public MockCloudTable(Uri tableAbsoluteUri, StorageCredentials credentials) : base(tableAbsoluteUri, credentials)
    { }

    public async override Task<TableResult> ExecuteAsync(TableOperation operation)
    {
        return await Task.FromResult(new TableResult
        {
            Result = new ScreenSettingEntity() { Settings = "" },
            HttpStatusCode = 200
        });
    }
}

I instantiated the mock class by passing an configuration string used for local storage by the storage emulator (see https://learn.microsoft.com/en-us/azure/storage/common/storage-configure-connection-string).

var mockTable = new MockCloudTable(new Uri("http://127.0.0.1:10002/devstoreaccount1/screenSettings"));

In this example 'screenSettings' is the name of the table.

The mock class can now be passed to the Azure Function from your unit test.

Maybe this is what you are looking for?

like image 53
Wilco Avatar answered Oct 20 '22 18:10

Wilco


I've faced the same scenario as the selected answer involving an Azure function with table binding. There are some limitations to using a mock CloudTable especially when using System.Linq with CreateQuery<T> for example as those are extension methods on IQueryable.

A better approach is to use a HttpMessageHandler mock such as RichardSzalay.MockHttp with TableClientConfiguration.RestExecutorConfiguration.DelegatingHandler and then just stubbing out the json response you'd expect from the table.

public class Azure_Function_Test_With_Table_Binding
{
    [Fact]
    public void Should_be_able_to_stub_out_a_CloudTable()
    {
        var storageAccount = StorageAccount.NewFromConnectionString("UseDevelopmentStorage=true");
        var client = storageAccount.CreateCloudTableClient();

        var mockedRequest = new MockHttpMessageHandler()
            .When("http://127.0.0.1:10002/devstoreaccount1/pizzas*")
            .Respond("application/json", 
            @"{
                ""value"": [
                    {
                        ""Name"": ""Pepperoni"",
                        ""Price"": 9.99
                    }
                ]
            }");

        client.TableClientConfiguration.RestExecutorConfiguration.DelegatingHandler = new MockedRequestAdapter(mockedRequest);
        var table = client.GetTableReference("pizzas");

        var request = new DefaultHttpContext().Request;
        request.Query = new QueryCollection(new Dictionary<string, StringValues> { { "Pizza", new StringValues("Pepperoni") } });

        var result = PizzaStore.Run(request, table, null);

        Assert.IsType<OkObjectResult>(result);
    }
}

public class MockedRequestAdapter : DelegatingHandler
{
    private readonly MockedRequest _mockedRequest;

    public MockedRequestAdapter(MockedRequest mockedRequest) : base()
    {
        _mockedRequest = mockedRequest;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        return await _mockedRequest.SendAsync(new HttpRequestMessage(request.Method, request.RequestUri), cancellationToken);
    }
}

public static class PizzaStore
{
    [FunctionName("PizzaStore")]
    public static IActionResult Run(
        [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req,
        [Table("pizzas", Connection = "AzureWebJobsStorage")] CloudTable cloud,
        ILogger log)
    {
        if (req.Query.TryGetValue("Pizza", out var value))
        {
            var pizza = cloud.CreateQuery<Pizza>().Where(p => p.Name == value.ToString()).SingleOrDefault();

            return new OkObjectResult(new { Pizza = pizza.Name, Price = pizza.Price });
        }

        return new NotFoundResult();
    }
}

public class Pizza : TableEntity
{
    public string Name { get; set; }
    public double Price { get; set; }
}
like image 36
Viv Avatar answered Oct 20 '22 19:10

Viv


To add to the answer here, as you're aiming to use a mocking framework, simply setting up an object that inherits from CloudTable and provides a default constructor should allow you to Mock the inherited object itself and control what it returns:

public class CloudTableMock : CloudTable
{
    public CloudTableMock() : base(new Uri("http://127.0.0.1:10002/devstoreaccount1/screenSettings"))
    {
    }
}

Then it's just a case of creating the mock. I'm using NSubstitute so I did:

_mockTable = Substitute.For<CloudTableMock>();

but my guess is that Moq would allow:

_mockTableRef = new Mock<CloudTable>();
_mockTableRef.Setup(x => x.DoSomething()).ReturnsAsync("My desired result");
_mockTable = _mockTableRef.Object;

(My Moq is a bit rusty, so I'm guessing the syntax above isn't quite correct)

like image 1
AdamCrawford Avatar answered Oct 20 '22 18:10

AdamCrawford