为 Azure table 存储模拟 CloudStorageAccount 和 CloudTable
Mocking CloudStorageAccount and CloudTable for Azure table storage
所以我正在尝试测试 Azure Table 存储和模拟我所依赖的东西。我的 class 的结构是在构造函数中建立连接,即我创建了一个 CloudStorageAccount
的新实例,在其中我创建了一个 StorageCredentials
的实例,该实例具有 storageName
和 storageKey
。之后,我创建了一个 CloudTable
的实例,我在代码中进一步使用它来执行 CRUD 操作。我的 class 看起来如下:
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
在整个 class 中被重复用于不同的目的。我的目标是模拟它,但由于它是虚拟的并且没有实现任何接口,所以我无法提出一个简单的 Mock
解决方案,例如:
_storageAccount = new Mock<CloudStorageAccount>(new Mock<StorageCredentials>(("dummy", "dummy"), true));
_table = new Mock<CloudTable>(_storageAccount.Object.CreateCloudTableClient().GetTableReference(_records));
因此,当我尝试以这种方式构建我的单元测试时,我得到:
Type to mock must be an interface or an abstract or non-sealed class.
我的目标是完成类似的事情:
_table.Setup(x => x.DoSomething()).ReturnsAsync("My desired result");
非常感谢任何想法!
我也在为绑定到 Azure Table 存储的 Azure 函数实施单元测试而苦苦挣扎。我终于使用派生的 CloudTable class 让它工作了,我可以在其中覆盖我使用的方法和 return 固定结果。
/// <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
});
}
}
我通过传递存储模拟器用于本地存储的配置字符串实例化了模拟 class(参见 https://docs.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"));
在此示例中 'screenSettings' 是 table 的名称。
现在可以将模拟 class 从单元测试传递到 Azure 函数。
也许这就是您要找的东西?
要在这里添加答案,因为您的目标是使用模拟框架,只需设置一个从 CloudTable 继承的对象并提供默认构造函数,就可以让您模拟继承的对象本身并控制它的内容returns:
public class CloudTableMock : CloudTable
{
public CloudTableMock() : base(new Uri("http://127.0.0.1:10002/devstoreaccount1/screenSettings"))
{
}
}
那么这只是一个创建模拟的案例。我正在使用 NSubstitute,所以我这样做了:
_mockTable = Substitute.For<CloudTableMock>();
但我猜最小起订量会允许:
_mockTableRef = new Mock<CloudTable>();
_mockTableRef.Setup(x => x.DoSomething()).ReturnsAsync("My desired result");
_mockTable = _mockTableRef.Object;
(我的最小起订量有点生疏,所以我猜上面的语法不太正确)
这是我的实现:
public class StorageServiceTest
{
IStorageService _storageService;
Mock<CloudStorageAccount> _storageAccount;
[SetUp]
public void Setup()
{
var c = new StorageCredentials("dummyStorageAccountName","DummyKey");
_storageAccount = new Mock<CloudStorageAccount>(c, true);
_storageService = new StorageService(_storageAccount.Object);
}
[Test]
[TestCase("ax0-1s", "random-1")]
public void get_content_unauthorized(string containerName,string blobName)
{
//Arrange
string expectOutputText = "Something on the expected blob";
Uri uri = new Uri("https://somethig.com//");
var blobClientMock = new Mock<CloudBlobClient>(uri);
_storageAccount.Setup(a => a.CreateCloudBlobClient()).Returns(blobClientMock.Object);
var cloudBlobContainerMock = new Mock<CloudBlobContainer>(uri);
blobClientMock.Setup(a => a.GetContainerReference(containerName)).Returns(cloudBlobContainerMock.Object);
var cloudBlockBlobMock = new Mock<CloudBlockBlob>(uri);
cloudBlobContainerMock.Setup(a => a.GetBlockBlobReference(blobName)).Returns(cloudBlockBlobMock.Object);
cloudBlockBlobMock.Setup(a => a.DownloadTextAsync()).Returns(Task.FromResult(expectOutputText));
//Act
var actual = _storageService.DownloadBlobAsString(containerName, blobName);
//Assert
Assert.IsNotNull(actual);
Assert.IsFalse(string.IsNullOrWhiteSpace(actual.Result));
Assert.AreEqual(actual.Result, expectOutputText);
}
}
服务class实施:
Task<string> IStorageService.DownloadBlobAsString(string containerName, string blobName)
{
var blobClient = this.StorageAccountClient.CreateCloudBlobClient();
var blobContainer = blobClient.GetContainerReference(containerName);
var blobReference = blobContainer.GetBlockBlobReference(blobName);
var blobContentAsString = blobReference.DownloadTextAsync();
return blobContentAsString;
}
我遇到了与所选答案相同的场景,涉及具有 table 绑定的 Azure 函数。使用模拟 CloudTable
有一些限制,尤其是当 System.Linq
与 CreateQuery<T>
一起使用时,因为这些是 IQueryable
.
上的扩展方法
更好的方法是使用 HttpMessageHandler
模拟,例如 RichardSzalay.MockHttp 和 TableClientConfiguration.RestExecutorConfiguration.DelegatingHandler
,然后将 json 您期望来自 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; }
}
所以我正在尝试测试 Azure Table 存储和模拟我所依赖的东西。我的 class 的结构是在构造函数中建立连接,即我创建了一个 CloudStorageAccount
的新实例,在其中我创建了一个 StorageCredentials
的实例,该实例具有 storageName
和 storageKey
。之后,我创建了一个 CloudTable
的实例,我在代码中进一步使用它来执行 CRUD 操作。我的 class 看起来如下:
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
在整个 class 中被重复用于不同的目的。我的目标是模拟它,但由于它是虚拟的并且没有实现任何接口,所以我无法提出一个简单的 Mock
解决方案,例如:
_storageAccount = new Mock<CloudStorageAccount>(new Mock<StorageCredentials>(("dummy", "dummy"), true));
_table = new Mock<CloudTable>(_storageAccount.Object.CreateCloudTableClient().GetTableReference(_records));
因此,当我尝试以这种方式构建我的单元测试时,我得到:
Type to mock must be an interface or an abstract or non-sealed class.
我的目标是完成类似的事情:
_table.Setup(x => x.DoSomething()).ReturnsAsync("My desired result");
非常感谢任何想法!
我也在为绑定到 Azure Table 存储的 Azure 函数实施单元测试而苦苦挣扎。我终于使用派生的 CloudTable class 让它工作了,我可以在其中覆盖我使用的方法和 return 固定结果。
/// <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
});
}
}
我通过传递存储模拟器用于本地存储的配置字符串实例化了模拟 class(参见 https://docs.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"));
在此示例中 'screenSettings' 是 table 的名称。
现在可以将模拟 class 从单元测试传递到 Azure 函数。
也许这就是您要找的东西?
要在这里添加答案,因为您的目标是使用模拟框架,只需设置一个从 CloudTable 继承的对象并提供默认构造函数,就可以让您模拟继承的对象本身并控制它的内容returns:
public class CloudTableMock : CloudTable
{
public CloudTableMock() : base(new Uri("http://127.0.0.1:10002/devstoreaccount1/screenSettings"))
{
}
}
那么这只是一个创建模拟的案例。我正在使用 NSubstitute,所以我这样做了:
_mockTable = Substitute.For<CloudTableMock>();
但我猜最小起订量会允许:
_mockTableRef = new Mock<CloudTable>();
_mockTableRef.Setup(x => x.DoSomething()).ReturnsAsync("My desired result");
_mockTable = _mockTableRef.Object;
(我的最小起订量有点生疏,所以我猜上面的语法不太正确)
这是我的实现:
public class StorageServiceTest
{
IStorageService _storageService;
Mock<CloudStorageAccount> _storageAccount;
[SetUp]
public void Setup()
{
var c = new StorageCredentials("dummyStorageAccountName","DummyKey");
_storageAccount = new Mock<CloudStorageAccount>(c, true);
_storageService = new StorageService(_storageAccount.Object);
}
[Test]
[TestCase("ax0-1s", "random-1")]
public void get_content_unauthorized(string containerName,string blobName)
{
//Arrange
string expectOutputText = "Something on the expected blob";
Uri uri = new Uri("https://somethig.com//");
var blobClientMock = new Mock<CloudBlobClient>(uri);
_storageAccount.Setup(a => a.CreateCloudBlobClient()).Returns(blobClientMock.Object);
var cloudBlobContainerMock = new Mock<CloudBlobContainer>(uri);
blobClientMock.Setup(a => a.GetContainerReference(containerName)).Returns(cloudBlobContainerMock.Object);
var cloudBlockBlobMock = new Mock<CloudBlockBlob>(uri);
cloudBlobContainerMock.Setup(a => a.GetBlockBlobReference(blobName)).Returns(cloudBlockBlobMock.Object);
cloudBlockBlobMock.Setup(a => a.DownloadTextAsync()).Returns(Task.FromResult(expectOutputText));
//Act
var actual = _storageService.DownloadBlobAsString(containerName, blobName);
//Assert
Assert.IsNotNull(actual);
Assert.IsFalse(string.IsNullOrWhiteSpace(actual.Result));
Assert.AreEqual(actual.Result, expectOutputText);
}
}
服务class实施:
Task<string> IStorageService.DownloadBlobAsString(string containerName, string blobName)
{
var blobClient = this.StorageAccountClient.CreateCloudBlobClient();
var blobContainer = blobClient.GetContainerReference(containerName);
var blobReference = blobContainer.GetBlockBlobReference(blobName);
var blobContentAsString = blobReference.DownloadTextAsync();
return blobContentAsString;
}
我遇到了与所选答案相同的场景,涉及具有 table 绑定的 Azure 函数。使用模拟 CloudTable
有一些限制,尤其是当 System.Linq
与 CreateQuery<T>
一起使用时,因为这些是 IQueryable
.
更好的方法是使用 HttpMessageHandler
模拟,例如 RichardSzalay.MockHttp 和 TableClientConfiguration.RestExecutorConfiguration.DelegatingHandler
,然后将 json 您期望来自 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; }
}