Raskolnikov
Raskolnikov

Reputation: 3999

How to write unitTest for methods using a stream as a parameter

I have class ImportProvider , and I want write unit test for Import method.

But this should be unit test, so I don't want to read from file to stream. Any idea?

public class ImportProvider : IImportProvider
{ 
     public bool Import(Stream stream)
     {
         //Do import

         return isImported;
     }
}

public interface IImportProvider
{
      bool Import(Stream input);
}

This is unit test:

[TestMethod]
public void ImportProvider_Test()
{
    // Arrange           
    var importRepository = new Mock<IImportRepository>(); 
    var imp = new ImportProvider(importRepository.Object);
    //Do setup...

    // Act
    var test_Stream = ?????????????
    // This working but not option:
    //test_Stream = File.Open("C:/ExcelFile.xls", FileMode.Open, FileAccess.Read);
    var result = imp.Import(test_Stream);

    // Assert    
    Assert.IsTrue(result);
}

Upvotes: 50

Views: 62122

Answers (6)

Gucu112
Gucu112

Reputation: 967

I know that it's an old question, but I stumbled upon similar issue when trying to mock Stream in my unit test. As many of you suggested here, we can use MemoryStream in many cases, but it starts to be problematic if you want to check what was written to already closed Stream.

Assuming I want to test following piece of code:

public static void ToJsonFile(object? value, string path)
{
    // Open write stream for specified path
    using var fileStream = FileSystem.WriteStream(path);

    // Parse object to JSON string as memory stream
    using var memoryStream = ToJsonStream(value, new MemoryStream());

    // Write memory stream to a file
    memoryStream.WriteTo(fileStream);
}

And I am able to mock WriteStream() method, because FileSystem is an interface:

public interface IFileSystem
{
    public Stream WriteStream(string path);
}

I am not able to simply pass MemoryStream as a mocked result of WriteStream() function as it will get disposed before I'll be able to check what's inside:

[Test]
public void WriteStream_NotWorking_Test()
{
    // Arrange - create file system mock
    var fsMock = new Mock<IFileSystem>();

    // Create new memory stream
    var ms = new MemoryStream();

    // Setup mock to return created memory stream
    fsMock.Setup(fs => fs.WriteStream(It.IsAny<string>()))
        .Returns(ms).Verifiable();

    // Inject mocked file system
    ParseSettings.FileSystem = fsMock.Object;

    // Act - call method
    Parse.ToJsonFile("hello", "any.json");

    // ERROR! - System.ObjectDisposedException : Cannot access a closed Stream.
    var data = Encoding.UTF8.GetString(ms.GetBuffer(), 0, (int)ms.Length);

    // Assert - check data
    Assert.That(data, Does.Contain("hello"));
}

What I've come up with is to return mocked MemoryStream that is using base calls, because otherwise the method under test would fail. During buffer writing it will grab what was written to the buffer and save it to variable. Then you can use saved data in assertion even though the MemoryStream was already disposed:

[Test]
public void WriteStream_Working_Test()
{
    // Arrange - create file system mock
    var fsMock = new Mock<IFileSystem>();

    // Create memory stream mock
    var msMock = new Mock<MemoryStream>() { CallBase = true };

    // Create variable for buffer content
    byte[] bufferData = [];

    // Setup memory stream mock to grab what was written to the buffer
    msMock.Setup(ms => ms.Write(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>()))
        .Callback<byte[], int, int>((buffer, offset, count) =>
        {
            var bytes = offset > bufferData.Length
                ? bufferData.Concat(Enumerable.Repeat<byte>(0, offset - bufferData.Length))
                : bufferData[0..offset];

            bufferData = bytes.Concat(buffer.Take(count)).ToArray();
        }).CallBase();

    // Setup file system mock to return mocked memory stream
    fsMock.Setup(fs => fs.WriteStream(It.IsAny<string>()))
        .Returns(msMock.Object).Verifiable();

    // Inject mocked file system
    ParseSettings.FileSystem = fsMock.Object;

    // Act - call method
    Parse.ToJsonFile("hello", "any.json");

    // Get data from buffer - this will work now as it is using saved buffer instead of closed stream
    var data = Encoding.UTF8.GetString(bufferData, 0, bufferData.Length);

    // Assert - check data
    Assert.That(data, Does.Contain("hello"));
}

I hope it'll help someone in the future as I spend quite some time to figure that out, trying some hardcore stuff like UnmanagedMemoryStream :)

Upvotes: 0

Alex from Jitbit
Alex from Jitbit

Reputation: 60822

If you're unit-testing code that accepts network streams, http streams, AWS S3 streams and other "non-seekable" streams, then using MemoryStream is not your best idea, because it's "seekable".

I.e. it's too nice and gentle, and allows all kinds of manipulations.

To battle-test code that works with these "rough" streams, close to real-life conditions I would suggest inheriting from a MemoryStream and then override .CanSeek (false) .Length (throw NotSupported) .Position setter (throw nonsupported) etc. etc.

Real life example: I was working with an image processing library, that accepted Stream as an input. My tests were working fine, because they were based on MemoryStreams and FileStreams. But once I deployed my code to production where I process images from Amazon S3, I got all kinds of exceptions because the image library was expecting a seekable stream.

Here's the code I use:

public class BadBoyStream : MemoryStream
{
    public BadBoyStream (byte[] buffer) : base(buffer) { }

    public override bool CanSeek => false;

    public override long Length => throw new NotSupportedException();

    public override long Position
    {
        get => base.Position;
        set => throw new NotSupportedException();
    }

    public override long Seek(long offset, SeekOrigin origin) => throw new NotImplementedException();

    public override void SetLength(long value) => throw new NotSupportedException();
}

Upvotes: 7

GazTheDestroyer
GazTheDestroyer

Reputation: 21261

Use a MemoryStream. Not sure what your function expects, but to stuff a UTF-8 string into it for example:

//Act
using (var test_Stream = new MemoryStream(Encoding.UTF8.GetBytes("whatever")))
{
    var result = imp.Import(test_Stream);

    // Assert    
    Assert.IsTrue(result);
}

EDIT: If you need an Excel file, and you are unable to read files from disk, could you add an Excel file as an embedded resource in your test project? See How to embed and access resources by using Visual C#

You can then read as a stream like this:

//Act
using (var test_Stream = this.GetType().Assembly.GetManifestResourceStream("excelFileResource"))
{
    var result = imp.Import(test_Stream);

    // Assert    
    Assert.IsTrue(result);
}

Upvotes: 84

MistyK
MistyK

Reputation: 6232

My solution is to wrap Stream within your own class let's name it StreamWrapper which implements interface IStream. Now change Import signature to

public bool Import(IStream stream)

Now you are able to mock that stream and if you want to use it in production code you should pass StreamWrapper otherwise Mock.

Upvotes: 0

Ignacio Soler Garcia
Ignacio Soler Garcia

Reputation: 21855

Use an isolation framework like JustMock, TypeMock or Microsoft Fakes and you will be able to mock the Stream.

The bad news is that all of them as far as I know are paid.

Upvotes: -2

James Lucas
James Lucas

Reputation: 2522

You can use a MemoryStream to provide a purely in-memory stream for your test.

Upvotes: 7

Related Questions