我正在尝试为AzureBlobRepository编写单元测试。存储库在构造函数中接收CloubBlobClient。我想模仿客户端,但这给了一个例外:
using (var mock = AutoMock.GetLoose())
{
var mockClient = mock.Mock<CloudBlobClient>();
}
无法在类型为“Microsoft.WindowsAzure.Storage.Blob.CloudBlobClient”的两个长度相等的构造函数之间进行选择。在注册组件时,使用UsingConstructor()配置方法显式选择构造函数。
当然,在我的单元测试中,我没有注册任何内容,因此该消息不是很有帮助。
我还尝试过其他方法,例如提供NameParameters,TypedParameters或调用mock.Create而不是mock.Mock,但我尝试的所有内容都会返回相同的异常消息。
(CloudBlobContainer也会出现同样的问题)
实现接口后更新 这是我写的单元测试的一个例子:
[TestMethod]
public void AzureBlobRepository_GetByIdAsync_ReturnsContent()
{
Guid blobId = Guid.NewGuid();
Guid directoryName = Guid.NewGuid();
string containerName = "unittest";
using (var mock = AutoMock.GetLoose())
{
var mockClient = mock.Mock<ICloudBlobClient>();
var mockContainer = mock.Mock<ICloudBlobContainer>();
var mockDirectory = mock.Mock<ICloudBlobDirectory>();
// notice that we're not using AutoMock here, it fails to create the mock
var mockBlob = new Mock<CloudBlockBlob>(new Uri($"http://tempuri.org/{containerName}/{directoryName}/{blobId}"));
mockBlob.Setup(m => m.DownloadTextAsync()).Returns(Task.FromResult("content"));
mockClient.Setup(m => m.GetContainerReference(containerName))
.Returns(mockContainer.Object);
mockContainer.Setup(m => m.GetDirectoryReference(directoryName.ToString()))
.Returns(mockDirectory.Object);
mockDirectory.Setup(m => m.GetBlockBlobReference(blobId.ToString()))
.Returns(mockBlob.Object);
var repository = mock.Create<AzureBlobRepository>(
new TypedParameter(typeof(ICloudBlobClient), mockClient.Object),
new NamedParameter("container", containerName),
new NamedParameter("directory", directoryName));
var result = repository.GetByIdAsync(blobId, directoryName).Result;
result.ShouldBe("content");
}
}
答案 0 :(得分:2)
应将这些类视为第三方实施问题。这意味着你无法控制它们,我们不应该嘲笑我们无法控制的东西。它们应该封装在您控制的抽象背后,并且可以在单独测试时根据需要进行模拟。
public interface ICloubBlobClient {
//...expose only the functionality I need
}
public class CloubBlobClientWrapper : ICloubBlobClient {
private readonly CloubBlobClient client;
public CloubBlobClientWrapper(CloubBlobClient client) {
this.client = client;
}
//...implement interface wrapping
}
由于这个原因,类应该依赖于抽象而不是结核。模拟混凝土类往往会产生影响
包装器不需要完全包装客户端,但可以聚合功能,以免暴露实现问题。
所以现在当你单独测试时,你可以模拟你控制的抽象
using (var mock = AutoMock.GetLoose()) {
var mockClient = mock.Mock<ICloudBlobClient>();
/// ...and the rest of the test.
}
答案 1 :(得分:0)
见CloudBlobContainer。此类型包含三个构造函数。创建实例类型需要构造函数。尝试输入代码new CloudBlobContainer
,您需要选择三个构造函数中的一个。 AutoMock无法知道构造函数必须选择什么。
您可以对AutoMock说明如何创建CloudBlobContainer
样品:
using (var mock = AutoMock.GetLoose())
{
mock.Provide<CloudBlobContainer, CloudBlobContainer>(new NamedParameter("uri", new Uri("your uri")));
var mockClient = mock.Mock<CloudBlobClient>();
}
答案 2 :(得分:0)
我设法使用NSubstitute
对其进行了模拟,我只模拟了我使用的功能。
/// <summary>
/// Create a mock for CloudBlobClient
/// </summary>
/// <param name="containerExists"></param>
/// <returns></returns>
private CloudBlobClient GetMock(bool containerExists = true)
{
var items = new List<IListBlobItem>();
var client = Substitute.For<CloudBlobClient>(new Uri("http://foo.bar/"), null);
var container = Substitute.For<CloudBlobContainer>(new Uri("http://foo.bar/"));
client.GetContainerReference(Arg.Any<string>()).Returns(container);
container.ExistsAsync(Arg.Any<CancellationToken>()).Returns(Task.FromResult(containerExists));
container.ListBlobsSegmentedAsync(Arg.Any<string>(), Arg.Any<bool>(),
Arg.Any<BlobListingDetails>(),
Arg.Any<int?>(),
Arg.Any<BlobContinuationToken>(),
Arg.Any<BlobRequestOptions>(),
Arg.Any<OperationContext>(),
Arg.Any<CancellationToken>())
.Returns(ci => new BlobResultSegment(items.ToArray(), null));
container.GetBlockBlobReference(Arg.Any<string>()).Returns(ci => GetBlockBlobMock(ci.ArgAt<string>(0), items));
return client;
}
/// <summary>
/// Create a mock for CloudBlockBlob
/// </summary>
/// <param name="name"></param>
/// <param name="listBlobItems"></param>
/// <returns></returns>
private CloudBlockBlob GetBlockBlobMock(string name, List<IListBlobItem> listBlobItems)
{
var created = DateTimeOffset.Now;
var bufferStream = new MemoryStream();
var blob = Substitute.For<CloudBlockBlob>(new Uri("https://foo.blob.core.windows.net/bar/" + name + ".txt"));
//We can't mock the value the normal way, use reflection to change its value!
blob.Properties.GetType().GetProperty(nameof(blob.Properties.Created)).SetValue(blob.Properties, created);
//we cant mock properties! (Dam this wont work)
blob.UploadFromStreamAsync(Arg.Any<Stream>(),
Arg.Any<AccessCondition>(),
Arg.Any<BlobRequestOptions>(),
Arg.Any<OperationContext>(),
Arg.Any<CancellationToken>()).Returns(ci => {
var stream = ci.Arg<Stream>();
stream.CopyTo(bufferStream);
listBlobItems.Add(blob);
return Task.CompletedTask;
});
blob.DownloadToStreamAsync(Arg.Any<Stream>(),
Arg.Any<AccessCondition>(),
Arg.Any<BlobRequestOptions>(),
Arg.Any<OperationContext>(),
Arg.Any<CancellationToken>()).Returns(ci =>
{
var stream = ci.Arg<Stream>();
bufferStream.Position = 0;
bufferStream.CopyTo(stream);
stream.Position = 0;
return Task.CompletedTask;
});
return blob;
}
我不是100%地确定其100%准确,但是它允许我运行一些单元测试!