Search code examples
c#azure-storagensubstituteazure-sdk-.net

Mocking Blobclient.DownloadToAsync(stream) using NSubstitute


I saw a different issue answer that is related but I can't get my method mocked.

I'm using the blobClient.DownloadToAsync(fileStream) method. I want to create multiple tests. A test for a download with an empty file, a file with only CSV headers and a file with CSV headers and a line.

I tried everything but my stream is empty when I run the test. Any suggestions?

Code that I'm using for a test with headers:

// Create a memory stream to simulate the downloaded content
var memoryStream = new MemoryStream();
var writer = new StreamWriter(memoryStream);
writer.WriteLine("Header1,Header2,Header3");
writer.Flush();
//memoryStream.Position = 0;

var t = BlobsModelFactory.BlobDownloadStreamingResult(memoryStream);
var val = Response.FromValue(t, Substitute.For<Response>());
var returnVal = val.GetRawResponse();

blobClient.DownloadToAsync(Arg.Any<Stream>()).Returns(returnVal);

I think that I used the correct BlobsModelFactory method. I debugged the code of Microsoft and searched for a similar model in the factory.

The code that I want to test is something like this. The clients are injected into my class.

public async Task<int> GetMyDoc(MyCommand command)
{
    var blobPath = command.blobPath;

    // Get container client
    var blobContainerClient = _blobServiceClient.GetBlobContainerClient(command.containerId);

    // Get the blob client for the blob path.
    var blobClient = blobContainerClient.GetBlobClient(blobPath);
                    
    using var fileStream = new MemoryStream();
    await blobClient.DownloadToAsync(fileStream);

    using var sr = new StreamReader(fileStream);
    fileStream.Position = 0;

    var rowNumber = 0;
    while (!sr.EndOfStream)
    {
        // Other logic will be done here but for simplicity just count the number of rows...
        rowNumber++;
    }

    return rowNumber;
}

Solution

  • You're mocking the Response but instead of using the Stream of the Response you're expecting the data to be copied to the Stream you supplied in the arguments. Once you notice that it's quite easy to mock the behavior:

    blobClientSub
        .DownloadToAsync(Arg.Do<Stream>(memoryStream.CopyTo))
        .Returns(responseSub);
    

    edit

    The full test case:

    [Fact]
    public async Task Document_contains_one_row()
    {
        // Arrange
        var blobServiceClient = Substitute.For<BlobServiceClient>();
        var blobContainerClient = Substitute.For<BlobContainerClient>();
        var blobClient = Substitute.For<BlobClient>();
        var response = Substitute.For<Response>();
    
        blobServiceClient
            .GetBlobContainerClient(Arg.Any<string>())
            .Returns(blobContainerClient);
        blobContainerClient
            .GetBlobClient(Arg.Any<string>())
            .Returns(blobClient);
    
        using var memoryStream = new MemoryStream();
        using (var writer = new StreamWriter(memoryStream, leaveOpen: true))
        {
            writer.WriteLine("Header1,Header2,Header3");
        }
        memoryStream.Position = 0;
    
        blobClient
            .DownloadToAsync(Arg.Do<Stream>(memoryStream.CopyTo))
            .Returns(response);
    
        // Act
        var myService = new MyService(blobServiceClient);
        var rowCount = await myService.GetMyDoc(new MyCommand
        {
            containerId = "containerId",
            blobPath = "blobPath"
        });
    
        // Assert
        Assert.Equal(1, rowCount);
    }
    

    Or even skip the additional MemoryStream:

    blobClient
        .DownloadToAsync(Arg.Do<Stream>(x =>
        {
            using var writer = new StreamWriter(x, leaveOpen: true);
            writer.WriteLine("Header1,Header2,Header3");
        }))
        .Returns(response);