C# 带有AutoFac和AutoMock的模拟CloudBlobClient

C# 带有AutoFac和AutoMock的模拟CloudBlobClient,c#,unit-testing,moq,autofac,azure-storage-blobs,C#,Unit Testing,Moq,Autofac,Azure Storage Blobs,我正试图为我的AzureBlobRespository编写单元测试。存储库在构造函数中接收CloubBlobClient。我想模拟客户端,但这会产生一个例外: using (var mock = AutoMock.GetLoose()) { var mockClient = mock.Mock<CloudBlobClient>(); } 使用(var mock=AutoMock.GetLoose()) { var mockClient=mock.mock(); } 无法在

我正试图为我的AzureBlobRespository编写单元测试。存储库在构造函数中接收CloubBlobClient。我想模拟客户端,但这会产生一个例外:

using (var mock = AutoMock.GetLoose())
{
    var mockClient = mock.Mock<CloudBlobClient>();
}
使用(var mock=AutoMock.GetLoose())
{
var mockClient=mock.mock();
}
无法在类型为“Microsoft.WindowsAzure.Storage.Blob.CloudBlobClient”的多个长度为2的构造函数之间进行选择。注册组件时,使用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");
    }
}
[TestMethod]
公共无效AzureBlobResposition_GetByIdAsync_ReturnsContent()
{    
Guid blobId=Guid.NewGuid();
Guid directoryName=Guid.NewGuid();
字符串containerName=“unittest”;
使用(var mock=AutoMock.GetLoose())
{
var mockClient=mock.mock();
var mockContainer=mock.mock();
var mockDirectory=mock.mock();
//请注意,我们这里没有使用AutoMock,它无法创建模拟
var mockBlob=new Mock(新Uri($)http://tempuri.org/{containerName}/{directoryName}/{blobId}”);
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(
新的TypedParameter(typeof(ICloudBlobClient)、mockClient.Object),
新名称参数(“容器”,容器名称),
新名称参数(“目录”,目录名));
var result=repository.GetByIdAsync(blobId,directoryName).result;
结果。应为(“内容”);
}
}
请参见此。此类型包含三个构造函数。类型的创建实例需要构造函数。尝试键入代码
newcloudblobcontainer
,您需要从三个构造函数中选择一个。AutoMock无法知道构造函数必须选择什么

您可以告诉AutoMock如何创建CloudBlobContainer

样本:

using (var mock = AutoMock.GetLoose())
{
    mock.Provide<CloudBlobContainer, CloudBlobContainer>(new NamedParameter("uri", new Uri("your uri")));
    var mockClient = mock.Mock<CloudBlobClient>();
}
使用(var mock=AutoMock.GetLoose())
{
mock.Provide(新名称参数(“uri”),新uri(“您的uri”));
var mockClient=mock.mock();
}

这些类应被视为第三方实现问题。这意味着你无法控制他们,我们不应该嘲笑我们无法控制的东西。它们应该封装在您可以控制的抽象后面,并且在隔离测试时可以根据需要进行模拟

public interface ICloudBlobClient {
    //...expose only the functionality I need
}

public class CloudBlobClientWrapper : ICloudBlobClient {
    private readonly CloudBlobClient client;

    public CloudBlobClientWrapper(CloudBlobClient client) {
        this.client = client;
    }

    //...implement interface wrapping
}
由于这个原因,类应该依赖于抽象,而不是具体化。模拟混凝土类往往会产生连锁反应

包装器不需要精确包装客户机,但可以聚合功能,从而不暴露实现问题

因此,现在在隔离测试时,您可以模拟您控制的抽象

using (var mock = AutoMock.GetLoose()) {
    var mockClient = mock.Mock<ICloudBlobClient>();

    /// ...and the rest of the test.
}
使用(var mock=AutoMock.GetLoose()){
var mockClient=mock.mock();
///…还有剩下的测试。
}

我已经设法用
NSubstitute
模拟了它,我只模拟了我使用的函数

//
///为CloudBlobClient创建一个模拟
/// 
/// 
/// 
私有CloudBlobClient GetMock(bool containerExists=true)
{
var items=新列表();
var client=Substitute.For(新Uri(“http://foo.bar/(“”,空);
var container=Substitute.For(新Uri(“http://foo.bar/"));
GetContainerReference(Arg.Any())。返回(container);
container.ExistsAsync(Arg.Any()).Returns(Task.FromResult(containerExists));
container.ListBlobsSegmentedAsync(Arg.Any(),Arg.Any(),
Arg.Any(),
Arg.Any(),
Arg.Any(),
Arg.Any(),
Arg.Any(),
Arg.Any())
.Returns(ci=>newblobresultsegment(items.ToArray(),null));
GetBlockBlobReference(Arg.Any())。返回(ci=>getBlockBlobLock(ci.ArgAt(0),items));
返回客户;
}
/// 
///为CloudBlockBlob创建一个模拟
/// 
/// 
/// 
/// 
私有CloudBlockBlob GetBlockBlobLock(字符串名称,列表ListBlobbItems)
{
var created=DateTimeOffset.Now;
var bufferStream=new MemoryStream();
var blob=Substitute.For(新Uri(“https://foo.blob.core.windows.net/bar/“+name+”.txt”);
//我们不能以正常的方式模拟该值,使用反射来更改其值!
blob.Properties.GetType().GetProperty(nameof(blob.Properties.Created)).SetValue(blob.Properties,Created);
//我们不能嘲笑财产!(这行不通)
blob.UploadFromStreamAsync(Arg.Any(),
Arg.Any(),
Arg.Any(),
Arg.Any(),
Arg.Any())。返回(ci=>{
var stream=ci.Arg();
CopyTo(bufferStream);
listBlobItems.Add(blob);
返回Task.CompletedTask;
});
blob.DownloadToStreamAsync(Arg.Any(),
Arg.Any(),