【问题标题】:How to catch passed parameter and change it's value in Mocked method如何捕获通过参数并以模拟方法更改它的值
【发布时间】:2021-10-25 09:18:16
【问题描述】:

我需要对从 Azure Blob 存储下载文件的非常简单的方法进行单元测试。 - 下载文件异步。这是全班。

public class BlobStorageService : IBlobStorageService
    {
        private readonly BlobServiceClient _blobStorageService;

        public BlobStorageService(IBlobStorageConnector blobStorageConnector)
        {
            var connector = blobStorageConnector ?? throw new ArgumentNullException(nameof(blobStorageConnector));

            _blobStorageService = connector.GetBlobStorageClient();
        }

        public async Task<Stream> DownloadFileAsync(string fileName, string containerName)
        {
            var container = _blobStorageService.GetBlobContainerClient(containerName);
            var blob = container.GetBlobClient(fileName);

            if (await blob.ExistsAsync())
            {
                using (var stream = new MemoryStream())
                {
                    await blob.DownloadToAsync(stream);

                    stream.Position = 0;
                    return stream;
                }
            }
            return Stream.Null;
        }
    }
}

问题是它需要大量的模拟。我对测试的想法很陌生,所以这可能是更好的方法。

public class BlobStorageServiceTests
    {
        private string _containerName = "containerTest";
        private string _blobName = "blob";

        [Fact]
        public async Task BlobStorageService_Should_Return_File()
        {
            // Arrange
            Mock<IBlobStorageConnector> connectorMock = new Mock<IBlobStorageConnector>();
            Mock<BlobServiceClient> blobServiceClientMock = new Mock<BlobServiceClient>();
            Mock<BlobContainerClient> blobContainerClientMock = new Mock<BlobContainerClient>();
            Mock<BlobClient> blobClientMock = new Mock<BlobClient>();
            Mock<Response<bool>> responseMock = new Mock<Response<bool>>();

            //Preparing result stream
            string testString = "testString";
            byte[] bytes = Encoding.ASCII.GetBytes(testString);
            Stream testStream = new MemoryStream(bytes);
            testStream.Position = 0;
            

            responseMock.Setup(x => x.Value).Returns(true);
            // this doesn't work, passed stream is not changed, does callback work with value not reference?
            blobClientMock.Setup(x => x.DownloadToAsync(It.IsAny<Stream>(), CancellationToken.None)).Callback<Stream, CancellationToken>((stm, token) => stm = testStream);
            blobClientMock.Setup(x => x.ExistsAsync(CancellationToken.None)).ReturnsAsync(responseMock.Object);
            
            blobContainerClientMock.Setup(x => x.GetBlobClient(_blobName)).Returns(blobClientMock.Object);
            blobServiceClientMock.Setup(x => x.GetBlobContainerClient(_containerName)).Returns(blobContainerClientMock.Object);

            connectorMock.Setup(x => x.GetBlobStorageClient()).Returns(blobServiceClientMock.Object);

            BlobStorageService blobStorageService = new BlobStorageService(connectorMock.Object); ;
            
            // Act

            var result = await blobStorageService.DownloadFileAsync(_blobName, _containerName);

            StreamReader reader = new StreamReader(result);
            string stringResult = reader.ReadToEnd();

            // Assert

            stringResult.Should().Contain(testString);
        
        }
    }

一切都像一个魅力,只有一小部分测试会导致问题。

确切地说是这部分:


 // This callback works
            blobClientMock.Setup(x => x.ExistsAsync(CancellationToken.None)).ReturnsAsync(responseMock.Object).Callback(() => Trace.Write("inside job"));

// this doesn't work, does callback not fire?
            blobClientMock.Setup(x => x.DownloadToAsync(It.IsAny<Stream>(), CancellationToken.None)).ReturnsAsync(dynamicResponseMock.Object).Callback<Stream, CancellationToken>((stm, token) => Trace.Write("inside stream"));

//Part of tested class where callback should fire

if (await blob.ExistsAsync())
            {
                using (var stream = new MemoryStream())
                {
                    await blob.DownloadToAsync(stream);

                    stream.Position = 0;
                    return stream;
                }
            }

最后一部分的代码与开头的代码略有不同,我试图只写给 Trace。 “Inside Job”显示得很好,“Inside stream”根本没有。回调没有被触发吗?这里有什么问题?

【问题讨论】:

  • 你应该写入提供的流,而不是更改引用(参数不是作为ref 传递的,这怎么可能工作?)
  • 我已经改变了我的问题,因为它被问得很糟糕。

标签: c# unit-testing testing moq xunit


【解决方案1】:

如 cmets 中所述,您需要写入捕获的流,而不是替换它,以获得预期的行为

//...

blobClientMock
    .Setup(x => x.DownloadToAsync(It.IsAny<Stream>(), CancellationToken.None))
    .Returns((Stream stm, CancellationToken token) => testStream.CopyToAsync(stm, token));

//...

【讨论】:

  • 谢谢你们,这确实是一个问题,但更大的错误是我在模拟中使用了不同的方法,而不是我真正必须使用的方法。看来我需要一些假期。
【解决方案2】:

我知道这不是 100% 的答案,但您可以通过创建如下存根来解决模拟问题:

public sealed class StubBlobClient : BlobClient
{
    private readonly Response _response;

    public StubBlobClient(Response response)
    {
        _response = response;
    }

    public override Task<Response> DownloadToAsync(Stream destination)
    {
        using (var archive = new ZipArchive(destination, ZipArchiveMode.Create, true))
        {
            var jsonFile = archive.CreateEntry("file.json");
            using var entryStream = jsonFile.Open();
            using var streamWriter = new StreamWriter(entryStream);
            streamWriter.WriteLine(TrunkHealthBlobConsumerTests.TrunkHealthJsonData);
        }
        return Task.FromResult(_response);
    }
}

还有一个BlobContainerClient 的存根,如下所示:

public sealed class StubBlobContainerClient : BlobContainerClient
{
    private readonly BlobClient _blobClient;

    public StubBlobContainerClient(BlobClient blobClient)
    {
        _blobClient = blobClient;
    }

    public override AsyncPageable<BlobHierarchyItem> GetBlobsByHierarchyAsync(
        BlobTraits traits = BlobTraits.None,
        BlobStates states = BlobStates.None,
        string delimiter = default,
        string prefix = default,
        CancellationToken cancellationToken = default)
    {
        var item = BlobsModelFactory.BlobHierarchyItem("some prefix", BlobsModelFactory.BlobItem(name: "trunk-health-regional.json"));

        Page<BlobHierarchyItem> page = Page<BlobHierarchyItem>.FromValues(new[] { item }, null, null);

        var pages = new[] { page };

        return AsyncPageable<BlobHierarchyItem>.FromPages(pages);
    }

    public override BlobClient GetBlobClient(string prefix)
    {
        return _blobClient;
    }
}

然后像这样简单地安排你的测试:

var responseMock = new Mock<Response>();
var blobClientStub = new StubBlobClient(responseMock.Object);
var blobContainerClientStub = new StubBlobContainerClient(blobClientStub);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-07-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-26
    • 1970-01-01
    • 2015-05-24
    • 1970-01-01
    相关资源
    最近更新 更多